#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Copyright 2020 SkyWater PDK Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # 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. # # SPDX-License-Identifier: Apache-2.0 import argparse import enum import json import os import pathlib import pprint import re import sys from collections import defaultdict from typing import Tuple, List, Dict from math import frexp, log2 from . import sizes from .utils import sortable_extracted_numbers debug = False LOG2_10 = log2(10) class TimingType(enum.IntFlag): """ >>> TimingType.parse("ff_100C_1v65") ('ff_100C_1v65', ) >>> TimingType.parse("ff_100C_1v65_ccsnoise") ('ff_100C_1v65', ) >>> TimingType.basic in TimingType.ccsnoise True >>> TimingType.parse("ff_100C_1v65_pwrlkg") ('ff_100C_1v65', ) >>> (TimingType.basic).describe() '' >>> (TimingType.ccsnoise).describe() '(with ccsnoise)' >>> (TimingType.leakage).describe() '(with power leakage)' >>> (TimingType.leakage | TimingType.ccsnoise).describe() '(with ccsnoise and power leakage)' >>> (TimingType.leakage | TimingType.ccsnoise).names() 'basic, ccsnoise, leakage' >>> TimingType.ccsnoise.names() 'basic, ccsnoise' """ basic = 1 # ccsnoise files are basic files with extra 'ccsn_' values in the timing # data. ccsnoise = 2 | basic # leakage files are separate from the basic files leakage = 4 def names(self): o = [] for t in TimingType: if t in self: o.append(t.name) return ", ".join(o) def describe(self): o = [] if TimingType.ccsnoise in self: o.append("ccsnoise") if TimingType.leakage in self: o.append("power leakage") if not o: return "" return "(with "+" and ".join(o)+")" @property def file(self): if self == TimingType.ccsnoise: return "_ccsnoise" elif self == TimingType.leakage: return "_pwrlkg" return "" @classmethod def parse(cls, name): ttype = TimingType.basic if name.endswith("_ccsnoise"): name = name[:-len("_ccsnoise")] ttype = TimingType.ccsnoise elif name.endswith("_pwrlkg"): name = name[:-len("_pwrlkg")] ttype = TimingType.leakage return name, ttype @property def singular(self): return len(self.types) == 1 @property def types(self): tt = set(t for t in TimingType if t in self) if TimingType.ccsnoise in tt: tt.remove(TimingType.basic) return list(tt) def cell_corner_file(lib, cell_with_size, corner, corner_type: TimingType): """ >>> cell_corner_file("sky130_fd_sc_hd", "a2111o", "ff_100C_1v65", TimingType.basic) 'cells/a2111o/sky130_fd_sc_hd__a2111o__ff_100C_1v65.lib.json' >>> cell_corner_file("sky130_fd_sc_hd", "a2111o_1", "ff_100C_1v65", TimingType.basic) 'cells/a2111o/sky130_fd_sc_hd__a2111o_1__ff_100C_1v65.lib.json' >>> cell_corner_file("sky130_fd_sc_hd", "a2111o_1", "ff_100C_1v65", TimingType.ccsnoise) 'cells/a2111o/sky130_fd_sc_hd__a2111o_1__ff_100C_1v65_ccsnoise.lib.json' """ assert corner_type.singular, (lib, cell_with_size, corner, corner_type, corner_type.types()) sz = sizes.parse_size(cell_with_size) if sz: cell = cell_with_size[:-len(sz.suffix)] else: cell = cell_with_size fname = "cells/{cell}/{lib}__{cell_sz}__{corner}{corner_type}.lib.json".format( lib=lib, cell=cell, cell_sz=cell_with_size, corner=corner, corner_type=corner_type.file) return fname def top_corner_file(libname, corner, corner_type: TimingType, directory_prefix = "timing"): """ >>> top_corner_file("sky130_fd_sc_hd", "ff_100C_1v65", TimingType.ccsnoise) 'timing/sky130_fd_sc_hd__ff_100C_1v65_ccsnoise.lib.json' >>> top_corner_file("sky130_fd_sc_hd", "ff_100C_1v65", TimingType.basic) 'timing/sky130_fd_sc_hd__ff_100C_1v65.lib.json' >>> top_corner_file("sky130_fd_sc_hd", "ff_100C_1v65", TimingType.basic, "") 'sky130_fd_sc_hd__ff_100C_1v65.lib.json' """ assert corner_type.singular, (libname, corner, corner_type, corner_type.types()) if directory_prefix: return "{prefix}/{libname}__{corner}{corner_type}.lib.json".format( libname=libname, corner=corner, corner_type=corner_type.file, prefix = directory_prefix) return "{libname}__{corner}{corner_type}.lib.json".format( libname=libname, corner=corner, corner_type=corner_type.file) def collect(library_dir) -> Tuple[Dict[str, TimingType], List[str]]: """Collect the available timing information in corners. Parameters ---------- library_dir: str Path to a library. Returns ------- lib : str Library name corners : {str: TimingType} corners in the library. cells : list of str cells in the library. """ if not isinstance(library_dir, pathlib.Path): library_dir = pathlib.Path(library_dir) libname0 = None corners = {} all_cells = set() for p in library_dir.rglob("*.lib.json"): if not p.is_file(): continue if "timing" in str(p): continue fname, fext = str(p.name).split('.', 1) libname, cellname, corner = fname.split("__") if libname0 is None: libname0 = libname assert libname0 == libname, (libname0, libname) corner_name, corner_type = TimingType.parse(corner) if corner_name not in corners: corners[corner_name] = [corner_type, set()] corners[corner_name][0] |= corner_type corners[corner_name][1].add(cellname) all_cells.add(cellname) for c in corners: corners[c] = (corners[c][0], list(sorted(corners[c][1]))) assert corners, library_dir assert all_cells, library_dir assert libname0, library_dir all_cells = list(sorted(all_cells)) # Sanity check to make sure the corner exists for all cells. for corner, (corner_types, corner_cells) in sorted(corners.items()): missing = set() for cell_with_size in all_cells: if cell_with_size not in corner_cells: missing.add(cell_with_size) if not missing: continue print("Missing", ", ".join(missing), "from", corner, corner_types) return libname0, corners, all_cells for corner, (corner_types, corner_cells) in sorted(corners.items()): for corner_type in corner_types.types: fname = cell_corner_file(libname0, cell_with_size, corner, corner_type) fpath = os.path.join(library_dir, fname) if not os.path.exists(fpath) and debug: print("Missing", (fpath, corner, corner_type, corner_types)) timing_dir = os.path.join(library_dir, "timing") assert os.path.exists(timing_dir), timing_dir for corner, (corner_types, corner_cells) in sorted(corners.items()): for corner_type in corner_types.types: fname = top_corner_file(libname0, corner, corner_type) fpath = os.path.join(library_dir, fname) if not os.path.exists(fpath) and debug: print("Missing", (fpath, corner, corner_type, corner_types)) return libname0, corners, all_cells def remove_ccsnoise_from_timing(data, dataname): assert "timing" in data, (dataname, data.keys(), data) timing = data["timing"] if isinstance(timing, list): for i, t in enumerate(timing): assert isinstance(t, dict), (dataname, i, t) remove_ccsnoise_from_dict(t, "{}.timing[{:3d}]".format(dataname, i)) elif isinstance(timing, dict): remove_ccsnoise_from_dict(timing, dataname+".timing") else: assert False, (dataname, type(timing), timing) def remove_ccsnoise_from_dict(data, dataname): if "timing" in data: remove_ccsnoise_from_timing(data, dataname) ccsn_keys = set() for k in data: if "ccsn_" in k: ccsn_keys.add(k) for k in ccsn_keys: if debug: print("{:s}: Removing {}".format(dataname, k)) del data[k] def remove_ccsnoise_from_cell(data, cellname): remove_ccsnoise_from_dict(data, cellname) for k, v in list(data.items()): if k.startswith("pin "): pin_data = data[k] if "input_voltage" in pin_data: del pin_data["input_voltage"] remove_ccsnoise_from_dict(pin_data, "{}.{}".format(cellname, k)) if k.startswith("bus"): bus_data = data[k] remove_ccsnoise_from_dict(bus_data, "{}.{}".format(cellname, k)) remove_ccsnoise_from_library = remove_ccsnoise_from_dict def generate(library_dir, lib, corner, ocorner_type, icorner_type, cells, output_directory): output_directory_prefix = None if output_directory else "timing" top_fname = top_corner_file(lib, corner, ocorner_type, output_directory_prefix).replace('.lib.json', '.lib') output_directory = output_directory if output_directory else library_dir top_fpath = os.path.join(output_directory, top_fname) top_fout = open(top_fpath, "w") def top_write(lines): print("\n".join(lines), file=top_fout) otype_str = "({} from {})".format(ocorner_type.name, icorner_type.names()) print("Starting to write", top_fpath, otype_str, flush=True) common_data = {} common_data_path = os.path.join(library_dir, "timing", "{}__common.lib.json".format(lib)) assert os.path.exists(common_data_path), common_data_path with open(common_data_path) as f: d = json.load(f) assert isinstance(d, dict) for k, v in d.items(): assert k not in common_data, (k, common_data[k]) common_data[k] = v top_data_path = os.path.join(library_dir, top_corner_file(lib, corner, icorner_type)) assert os.path.exists(top_data_path), top_data_path with open(top_data_path) as f: d = json.load(f) assert isinstance(d, dict) for k, v in d.items(): if k in common_data: print("Overwriting", k, "with", v, "(existing value of", common_data[k], ")") common_data[k] = v # Remove the ccsnoise if it exists if ocorner_type != TimingType.ccsnoise: remove_ccsnoise_from_library(common_data, "library") attribute_types = {} output = liberty_dict("library", lib+"__"+corner, common_data, attribute_types) assert output[-1] == '}', output top_write(output[:-1]) for cell_with_size in cells: fname = cell_corner_file(lib, cell_with_size, corner, icorner_type) fpath = os.path.join(library_dir, fname) assert os.path.exists(fpath), fpath with open(fpath) as f: cell_data = json.load(f) # Remove the ccsnoise if it exists if ocorner_type != TimingType.ccsnoise: remove_ccsnoise_from_cell(cell_data, cell_with_size) top_write(['']) top_write(liberty_dict( "cell", "%s__%s" % (lib, cell_with_size), cell_data, [cell_with_size], attribute_types, )) top_write(['']) top_write(['}']) top_fout.close() print(" Finish writing", top_fpath, flush=True) print("") # * The 'delay_model' should be the 1st attribute in the library # * The 'technology' should be the 1st attribute in the library LIBERTY_ATTRIBUTE_ORDER = re.sub('/\\*[^*]*\\*/', '', """ library (name_string) { /* Library-Level Simple and Complex Attributes */ define (...,...,...) ; technology (name_enum) ; delay_model : "model" ; bus_naming_style : "string" ; date : "date" ; comment : "string" ; /* Unit definitions */ time_unit : "unit" ; voltage_unit : "unit" ; leakage_power_unit : "unit" ; current_unit : "unit" ; pulling_resistance_unit : "unit" ; ..._unit : "unit" ; /* FIXME: Should capacitive_load_unit always be last? */ capacitive_load_unit (value, unit) ; /* FIXME: Why is define_cell_area here, while other defines are up above? */ define_cell_area (area_name, resource_type) ; revision : float | string ; /* Default Attributes and Values */ default_cell_leakage_power : float ; default_fanout_load : float ; default_inout_pin_cap : float ; default_input_pin_cap : float ; default_max_transition : float ; default_output_pin_cap : float ; default_... : ... ; /* Scaling Factors Attributes and Values */ k_process_cell_fall ... ; k_process_cell_rise ... ; k_process_fall_propagation ... ; k_process_fall_transition ... ; k_process_rise_propagation ... ; k_process_rise_transition ... ; k_temp_cell_fall ... ; k_temp_cell_rise ... ; k_temp_fall_propagation ... ; k_temp_fall_transition ... ; k_temp_rise_propagation ... ; k_temp_rise_transition ... ; k_volt_cell_fall ... ; k_volt_cell_rise ... ; k_volt_fall_propagation ... ; k_volt_fall_transition ... ; k_volt_rise_propagation ... ; k_volt_rise_transition ... ; k_... : ... ; /* Library-Level Group Statements */ operating_conditions (name_string) { ... operating conditions description ... } wire_load (name_string) { ... wire load description ... } wire_load_selection (name_string) { ... wire load selection criteria... } power_lut_template (namestring) { ... power lookup table template information... } lu_table_template (name_string) { variable_1 : value_enum ; variable_2 : value_enum ; variable_3 : value_enum ; index_1 ("float, ..., float"); index_2 ("float, ..., float"); index_3 ("float, ..., float"); } normalized_driver_waveform (waveform_template_name) { driver_waveform_name : string; /* Specifies the name of the driver waveform table */ index_1 ("float, ... float"); /* Specifies input net transition */ index_2 ("float, ... float"); /* Specifies normalized voltage */ values ("float, ... float", \ /* Specifies the time in library units */ ... , \\ "float, ... float"); } /* Cell definitions */ cell (namestring2) { ... cell description ... } ... /* FIXME: What are these and why are they last */ type (namestring) { ... type description ... } input_voltage (name_string) { ... input voltage information ... } output_voltage (name_string) { ... output voltage information ... } } """) RE_LIBERTY_LIST = re.compile("(.*)_([0-9]+)") RE_NUMBERS = re.compile('([0-9]+)') def _lookup_attribute_pos(name): # Pad with spaces so you don't get substring matches. name = ' ' + name if name.endswith('_'): name = name + ' ' i = LIBERTY_ATTRIBUTE_ORDER.find(name) if i != -1: return float(i) return None def liberty_attribute_order(attr_name): """ FIXME: Make these doctests less fragile... >>> liberty_attribute_order("define") (33.0, 0.0) >>> liberty_attribute_order('voltage_map') (inf, inf) >>> liberty_attribute_order('slew_lower_threshold_pct_fall') (inf, inf) >>> liberty_attribute_order('time_unit') (203.0, 0.0) >>> liberty_attribute_order('random_unit') (357.0, 0.0) >>> liberty_attribute_order('capacitive_load_unit') (386.0, 0.0) >>> liberty_attribute_order('technology') (60.0, 0.0) >>> liberty_attribute_order('technology("cmos")') (60.0, 0.0) >>> liberty_attribute_order('delay_model') (89.0, 0.0) >>> liberty_attribute_order("cell") (2282.0, 0.0) >>> v1, v2 = "variable_1", "variable_2" >>> i1, i2, i3, i4 = "index_1", "index_2", "index_3", "index_4" >>> print('\\n'.join(sorted([v2, i1, v1, i2, i3, i4], key=liberty_attribute_order))) variable_1 variable_2 index_1 index_2 index_3 index_4 >>> liberty_attribute_order("values") (2182.0, 0.0) >>> print('\\n'.join(sorted([ ... 'default_inout_pin_cap', ... 'k_XXXX', ... 'k_temp_cell_fall', ... 'default_XXXX', ... ], key=liberty_attribute_order))) default_inout_pin_cap default_XXXX k_temp_cell_fall k_XXXX """ assert ':' not in attr_name, attr_name m = RE_LIBERTY_LIST.match(attr_name) if m: k, n = m.group(1), m.group(2) i = _lookup_attribute_pos(k) if not i: i = float('inf') return float(i), float(n) lookup_name = attr_name i = _lookup_attribute_pos(lookup_name) if i: return i, 0.0 if '(' in lookup_name: lookup_name = lookup_name[:lookup_name.index('(')] if 'default_' in attr_name: lookup_name = 'default_...' if '_unit' in attr_name: lookup_name = '..._unit' if 'k_' in attr_name: lookup_name = 'k_...' i = _lookup_attribute_pos(lookup_name) if i: return i, 0.0 return float('inf'), float('inf') def is_liberty_list(k): """ >>> is_liberty_list("variable_1") True >>> is_liberty_list("index_3") True >>> is_liberty_list("values") True """ m = RE_LIBERTY_LIST.match(k) if m: k, n = m.group(1), m.group(2) return k in ('variable', 'index', 'values') def liberty_guess(o): """ >>> liberty_guess('hello') # doctest: +ELLIPSIS >>> liberty_guess(1.0) # doctest: +ELLIPSIS >>> liberty_guess(1) # doctest: +ELLIPSIS >>> liberty_guess(None) Traceback (most recent call last): ... ValueError: None has unguessable type: """ if isinstance(o, str): return liberty_str elif isinstance(o, (float,int)): return liberty_float else: raise ValueError("%r has unguessable type: %s" % (o, type(o))) def liberty_bool(b): """ >>> liberty_bool(True) 'true' >>> liberty_bool(False) 'false' >>> liberty_bool(1.0) 'true' >>> liberty_bool(1.5) Traceback (most recent call last): ... ValueError: 1.5 is not a bool >>> liberty_bool(0.0) 'false' >>> liberty_bool(0) 'false' >>> liberty_bool(1) 'true' >>> liberty_bool("error") Traceback (most recent call last): ... ValueError: 'error' is not a bool """ try: b2 = bool(b) except ValueError: b2 = None if b2 != b: raise ValueError("%r is not a bool" % b) return {True: 'true', False: 'false'}[b] def liberty_str(s): """ >>> liberty_str("hello") '"hello"' >>> liberty_str('he"llo') Traceback (most recent call last): ... ValueError: '"' is not allow in the string: 'he"llo' >>> liberty_str(1.0) '"1.0000000000"' >>> liberty_str(1) '"1.0000000000"' >>> liberty_str([]) Traceback (most recent call last): ... ValueError: [] is not a string >>> liberty_str(True) Traceback (most recent call last): ... ValueError: True is not a string """ try: if isinstance(s, (int, float)): s = liberty_float(s) except ValueError: pass if not isinstance(s, str): raise ValueError("%r is not a string" % s) if '"' in s: raise ValueError("'\"' is not allow in the string: %r" % s) return '"'+s+'"' def liberty_int(f): """ >>> liberty_int(1.0) 1 >>> liberty_int(1.5) Traceback (most recent call last): ... ValueError: 1.5 is not an int >>> liberty_int("error") Traceback (most recent call last): ... ValueError: 'error' is not an int """ try: f2 = int(f) except ValueError as e: f2 = None if f2 is None or f2 != f: raise ValueError("%r is not an int" % f) return int(f) def liberty_float(f): """ >>> liberty_float(1.9208818e-02) '0.0192088180' >>> liberty_float(1.5) '1.5000000000' >>> liberty_float(1e20) '1.000000e+20' >>> liberty_float(1) '1.0000000000' >>> liberty_float(True) Traceback (most recent call last): ... ValueError: True is not a float >>> liberty_float(False) Traceback (most recent call last): ... ValueError: False is not a float >>> liberty_float(0) '0.0000000000' >>> liberty_float(None) Traceback (most recent call last): ... ValueError: None is not a float >>> liberty_float('hello') Traceback (most recent call last): ... ValueError: 'hello' is not a float """ try: r = float(f) except (ValueError, TypeError): r = None if isinstance(f, bool): r = None if f is None or r != f: raise ValueError("%r is not a float" % f) width = 11 mag = int(frexp(r)[1]/LOG2_10) if mag > 9: return f'%{width}e' % r if mag < 0: return f"%{width+1}.{width-1}f" % r else: return f"%{width+1}.{width-mag-1}f" % r LIBERTY_ATTRIBUTE_TYPES = { 'boolean': liberty_bool, 'string': liberty_str, 'int': liberty_int, 'float': liberty_float, } INDENT=" " def liberty_composite(k, v, i=tuple()): """ >>> def pl(l): ... print("\\n".join(l)) >>> pl(liberty_composite("capacitive_load_unit", [1.0, "pf"], [])) capacitive_load_unit(1.0000000000, "pf"); >>> pl(liberty_composite("voltage_map", [("vpwr", 1.95), ("vss", 0.0)], [])) voltage_map("vpwr", 1.9500000000); voltage_map("vss", 0.0000000000); >>> pl(liberty_composite("library_features", 'report_delay_calculation', [])) library_features("report_delay_calculation"); """ if isinstance(v, tuple): v = list(v) if not isinstance(v, list): v = [v] #assert isinstance(v, list), (k, v) if isinstance(v[0], (list, tuple)): o = [] for j, l in enumerate(v): o.extend(liberty_composite(k, l, i)) return o o = [] for l in v: if isinstance(l, (float, int)): o.append(liberty_float(l)) elif isinstance(l, str): assert '"' not in l, (k, v) o.append('"%s"' % l) else: raise ValueError("%s - %r (%r)" % (k, l, v)) return ["%s%s(%s);" % (INDENT*len(i), k, ", ".join(o))] def liberty_join(l): """ >>> l = [5, 1.0, 10] >>> liberty_join(l)(l) '5.0000000000, 1.0000000000, 10.000000000' >>> l = [1, 5, 8] >>> liberty_join(l)(l) '1, 5, 8' """ d = defaultdict(lambda: 0) for i in l: d[type(i)] += 1 def types(l): return [(i, type(i)) for i in l] if d[float] > 0: assert (d[float]+d[int]) == len(l), (d, types(l)) def join(l): return ", ".join(liberty_float(f) for f in l) return join elif d[int] > 0: assert d[int] == len(l), (d, types(l)) def join(l): return ", ".join(str(f) for f in l) return join raise ValueError("Invalid value: %r" % types(l)) def liberty_list(k, v, i=tuple()): o = [] if isinstance(v[0], list): o.append('%s%s(' % (INDENT*len(i), k)) join = liberty_join(v[0]) for l in v: o.append('%s"%s", \\' % (INDENT*(len(i)+1), join(l))) o0 = o.pop(0) o[0] = o0+o[0].lstrip() o[-1] = o[-1][:-3] + ');' else: join = liberty_join(v) o.append('%s%s("%s");' % (INDENT*len(i), k, join(v))) return o def liberty_dict(dtype, dvalue, data, indent=tuple(), attribute_types=None): """ >>> def g(a, b, c): ... return {"group_name": a, "attribute_name":b, "attribute_type": c} >>> d = {'float': 1.0, "str": "str"} >>> print('\\n'.join(liberty_dict("library", "test", d))) library ("test") { float : 1.0000000000; str : "str"; } >>> d['define'] = [g("cell", "float", "string")] >>> print('\\n'.join(liberty_dict("library", "test", d))) library ("test") { define(float,cell,string); float : 1.0000000000; str : "str"; } >>> d['define'] = [g("library", "float", "string")] >>> print('\\n'.join(liberty_dict("library", "test", d))) library ("test") { define(float,library,string); float : "1.0000000000"; str : "str"; } """ assert isinstance(data, dict), (dtype, dvalue, data) if attribute_types is None: attribute_types = {} assert isinstance(attribute_types, dict), (dtype, dvalue, attribute_types) o = [] if dvalue: dbits = dvalue.split(",") for j, d in enumerate(dbits): if '"' in d: assert d.startswith('"'), (dvalue, dbits, indent) assert d.endswith('"'), (dvalue, dbits, indent) dbits[j] = d[1:-1] dvalue = ','.join('"%s"' % d.strip() for d in dbits) o.append('%s%s (%s) {' % (INDENT*len(indent), dtype, dvalue)) # Sort the attributes def attr_sort_key(item): k, v = item if "," in k: ktype, kvalue = k.split(",", 1) sortable_kv = sortable_extracted_numbers(kvalue) else: ktype = k kvalue = "" sortable_kv = tuple() if ktype == "comp_attribute": sortable_kt = liberty_attribute_order(kvalue) else: sortable_kt = liberty_attribute_order(ktype) return sortable_kt, ktype, sortable_kv, kvalue, k, v di = [attr_sort_key(i) for i in data.items()] di.sort() if debug: print(" "*len(str(indent)), "s1 s2 ", "%-40s" % "ktype", '%-40r' % "kvalue", "value") print("-"*len(str(indent)), "---- ---- ", "-"*40, "-"*40, "-"*44) for sk, kt, skv, kv, k, v in di: print(str(indent), "%4.0f %4.0f --" % sk, "%-40s" % kt, '%-40r' % kv, end=" ") sv = str(v) print(sv[:40], end=" ") if len(sv) > 40: print('...', end=" ") print() # Output all the attributes if dtype not in attribute_types: dtype_attribute_types = {} attribute_types[dtype] = dtype_attribute_types dtype_attribute_types = attribute_types[dtype] for _, ktype, _, kvalue, k, v in di: indent_n = list(indent)+[k] if ktype == 'define': for d in sorted(data['define'], key=lambda d: d['group_name']+'.'+d['attribute_name']): aname = d['attribute_name'] gname = d['group_name'] atype = d['attribute_type'] o.append('%sdefine(%s,%s,%s);' % ( INDENT*len(indent_n), aname, gname, atype)) assert atype in LIBERTY_ATTRIBUTE_TYPES, (atype, d) if gname not in attribute_types: attribute_types[gname] = {} attribute_types[gname][aname] = LIBERTY_ATTRIBUTE_TYPES[atype] elif ktype == "comp_attribute": o.extend(liberty_composite(kvalue, v, indent_n)) elif isinstance(v, dict): assert isinstance(v, dict), (dtype, dvalue, k, v) o.extend(liberty_dict(ktype, kvalue, v, indent_n, attribute_types)) elif isinstance(v, list): assert len(v) > 0, (dtype, dvalue, k, v) if isinstance(v[0], dict): def sk(o): return o.items() for l in sorted(v, key=sk): o.extend(liberty_dict(ktype, kvalue, l, indent_n, attribute_types)) elif is_liberty_list(ktype): o.extend(liberty_list(ktype, v, indent_n)) elif "table" == ktype: o.append('%s%s : "%s";' % (INDENT*len(indent_n), k, ",".join(v))) elif "clk_width" == ktype: for l in sorted(v): o.append('%s%s : "%s";' % (INDENT*len(indent_n), k, l)) else: raise ValueError("Unknown %s: %r\n%s" % ((ktype, kvalue, k), v, indent_n)) else: if ktype in dtype_attribute_types: liberty_out = dtype_attribute_types[ktype] else: liberty_out = liberty_guess(v) ov = liberty_out(v) o.append("%s%s : %s;" % (INDENT*len(indent_n), k, ov)) o.append("%s}" % (INDENT*len(indent))) return o def main(): parser = argparse.ArgumentParser() parser.add_argument( "library_path", help="Path to the library.", type=pathlib.Path, nargs=1) parser.add_argument( "corner", help="Corner to write output for.", default=None, nargs='*') parser.add_argument( "--ccsnoise", help="Include ccsnoise in file output.", action='store_true', default=False) parser.add_argument( "--leakage", help="Include power leakage in file output.", action='store_true', default=False) parser.add_argument( "--debug", help="Include verbose debug output on the console.", action='store_true', default=False) parser.add_argument( "-o", "--output_directory", help="Sets the parent directory of the liberty files", default="") args = parser.parse_args() if args.debug: global debug debug = True libdir = args.library_path[0] retcode = 0 lib, corners, all_cells = collect(libdir) if args.ccsnoise: output_corner_type = TimingType.ccsnoise elif args.leakage: output_corner_type = TimingType.leakage else: output_corner_type = TimingType.basic if args.corner == ['all']: args.corner = list(sorted(k for k, (v0, v1) in corners.items() if output_corner_type in v0)) if args.corner: for acorner in args.corner: if acorner in corners: continue print() print("Unknown corner:", acorner) retcode = 1 if retcode != 0: args.corner.clear() if not args.corner: print() print("Available corners for", lib+":") for k, v in sorted(corners.items()): print(" -", k, v[0].describe()) print() return retcode print("Generating", output_corner_type.name, "liberty timing files for", lib, "at", ", ".join(args.corner)) print() for corner in args.corner: input_corner_type, corner_cells = corners[corner] if output_corner_type not in input_corner_type: print("Corner", corner, "doesn't support", output_corner_type, "(only {})".format(input_corner_type)) return 1 if output_corner_type == TimingType.basic and TimingType.ccsnoise in input_corner_type: input_corner_type = TimingType.ccsnoise else: input_corner_type = output_corner_type generate( libdir, lib, corner, output_corner_type, input_corner_type, corner_cells, args.output_directory ) return 0 if __name__ == "__main__": import doctest fail, _ = doctest.testmod() if fail > 0: sys.exit(1) sys.exit(main())