#!/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 . import sizes from .utils import sortable_extracted_numbers debug = False 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): """ >>> 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' """ assert corner_type.singular, (libname, corner, corner_type, corner_type.types()) return "timing/{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): top_fname = top_corner_file(lib, corner, ocorner_type).replace('.lib.json', '.lib') top_fpath = os.path.join(library_dir, 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: f2 = float(f) except (ValueError, TypeError): f2 = None if isinstance(f, bool): f2 = None if f is None or f2 != f: raise ValueError("%r is not a float" % f) WIDTH = len(str(0.0083333333)) s = json.dumps(f) if 'e' in s: a, b = s.split('e') if '.' not in a: a += '.' while len(a)+len(b)+1 < WIDTH: a += '0' s = "%se%s" % (a, b) elif '.' in s: while len(s) < WIDTH: s += '0' else: if len(s) < WIDTH: s += '.' while len(s) < WIDTH: s += '0' return s 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) 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, ) return 0 if __name__ == "__main__": import doctest fail, _ = doctest.testmod() if fail > 0: sys.exit(1) sys.exit(main())