blob: 77f2bb58bc5cf18a2f3d260784c7df867b112c9d [file] [log] [blame]
#!/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())