| #!/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.basic: 1>) |
| |
| >>> TimingType.parse("ff_100C_1v65_ccsnoise") |
| ('ff_100C_1v65', <TimingType.ccsnoise: 3>) |
| |
| >>> TimingType.basic in TimingType.ccsnoise |
| True |
| |
| >>> TimingType.parse("ff_100C_1v65_pwrlkg") |
| ('ff_100C_1v65', <TimingType.leakage: 4>) |
| |
| >>> (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 |
| <function liberty_str at ...> |
| >>> liberty_guess(1.0) # doctest: +ELLIPSIS |
| <function liberty_float at ...> |
| >>> liberty_guess(1) # doctest: +ELLIPSIS |
| <function liberty_float at ...> |
| >>> liberty_guess(None) |
| Traceback (most recent call last): |
| ... |
| ValueError: None has unguessable type: <class 'NoneType'> |
| |
| """ |
| 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(1e9) |
| '1000000000.0' |
| |
| >>> liberty_float(1e10) |
| '1.000000e+10' |
| |
| >>> liberty_float(1e15) |
| '1.000000e+15' |
| |
| >>> 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()) |