api: Change the way liberty attributes are sorted.
Fixes #37, #40. The `LIBERTY_ATTRIBUTE_ORDER` template is used to control the ordering of the liberty attribute output. Signed-off-by: Tim 'mithro' Ansell <me@mith.ro>
This commit is contained in:
parent
ad9a4a4b23
commit
ee77597986
|
@ -32,6 +32,8 @@ from collections import defaultdict
|
||||||
from typing import Tuple, List, Dict
|
from typing import Tuple, List, Dict
|
||||||
|
|
||||||
from . import sizes
|
from . import sizes
|
||||||
|
from .utils import sortable_extracted_numbers
|
||||||
|
|
||||||
|
|
||||||
debug = False
|
debug = False
|
||||||
|
|
||||||
|
@ -346,30 +348,220 @@ def generate(library_dir, lib, corner, ocorner_type, icorner_type, cells):
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
|
|
||||||
INDENT=" "
|
# * 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 ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
# complex attribute -- (x,b)
|
|
||||||
|
|
||||||
RE_LIBERTY_LIST = re.compile("(.*)_([0-9]+)")
|
RE_LIBERTY_LIST = re.compile("(.*)_([0-9]+)")
|
||||||
|
RE_NUMBERS = re.compile('([0-9]+)')
|
||||||
|
|
||||||
def liberty_sort(k):
|
|
||||||
|
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):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
>>> liberty_sort("variable_1")
|
FIXME: Make these doctests less fragile...
|
||||||
(1, 'variable')
|
>>> liberty_attribute_order("define")
|
||||||
>>> liberty_sort("index_3")
|
(33.0, 0.0)
|
||||||
(3, 'index')
|
|
||||||
>>> liberty_sort("values") # doctest: +ELLIPSIS
|
>>> liberty_attribute_order('voltage_map')
|
||||||
(inf, 'values')
|
(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
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
m = RE_LIBERTY_LIST.match(k)
|
assert ':' not in attr_name, attr_name
|
||||||
|
|
||||||
|
m = RE_LIBERTY_LIST.match(attr_name)
|
||||||
if m:
|
if m:
|
||||||
k, n = m.group(1), m.group(2)
|
k, n = m.group(1), m.group(2)
|
||||||
n = int(n)
|
|
||||||
else:
|
i = _lookup_attribute_pos(k)
|
||||||
n = float('inf')
|
if not i:
|
||||||
return n, k
|
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):
|
def is_liberty_list(k):
|
||||||
|
@ -426,6 +618,9 @@ def liberty_float(f):
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
INDENT=" "
|
||||||
|
|
||||||
|
|
||||||
def liberty_composite(k, v, i=tuple()):
|
def liberty_composite(k, v, i=tuple()):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -522,88 +717,91 @@ def liberty_list(k, v, i=tuple()):
|
||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
def liberty_dict(dtype, dvalue, data, i=tuple()):
|
def liberty_dict(dtype, dvalue, data, indent=tuple()):
|
||||||
assert isinstance(data, dict), (dtype, dvalue, data)
|
assert isinstance(data, dict), (dtype, dvalue, data)
|
||||||
o = []
|
o = []
|
||||||
|
|
||||||
if dvalue:
|
if dvalue:
|
||||||
dbits = dvalue.split(",")
|
dbits = dvalue.split(",")
|
||||||
for j, d in enumerate(dbits):
|
for j, d in enumerate(dbits):
|
||||||
if '"' in d:
|
if '"' in d:
|
||||||
assert d.startswith('"'), (dvalue, dbits, i)
|
assert d.startswith('"'), (dvalue, dbits, indent)
|
||||||
assert d.endswith('"'), (dvalue, dbits, i)
|
assert d.endswith('"'), (dvalue, dbits, indent)
|
||||||
dbits[j] = d[1:-1]
|
dbits[j] = d[1:-1]
|
||||||
dvalue = ','.join('"%s"' % d.strip() for d in dbits)
|
dvalue = ','.join('"%s"' % d.strip() for d in dbits)
|
||||||
o.append('%s%s (%s) {' % (INDENT*len(i), dtype, dvalue))
|
o.append('%s%s (%s) {' % (INDENT*len(indent), dtype, dvalue))
|
||||||
|
|
||||||
i_n = list(i)+[(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()
|
||||||
|
|
||||||
# Output the attribute defines first
|
if ktype == "comp_attribute":
|
||||||
if 'define' in data:
|
sortable_kt = liberty_attribute_order(kvalue)
|
||||||
for d in sorted(data['define'], key=lambda d: d['group_name']+'.'+d['attribute_name']):
|
else:
|
||||||
o.append('%sdefine(%s,%s,%s);' % (INDENT*len(i_n), d['attribute_name'], d['group_name'], d['attribute_type']))
|
sortable_kt = liberty_attribute_order(ktype)
|
||||||
o.append('')
|
|
||||||
|
|
||||||
del data['define']
|
return sortable_kt, ktype, sortable_kv, kvalue, k, v
|
||||||
|
|
||||||
|
di = [attr_sort_key(i) for i in data.items()]
|
||||||
|
di.sort()
|
||||||
|
if debug:
|
||||||
|
for sk, kt, skv, kv, k, v in di:
|
||||||
|
print(str(indent), "%4.0f %4.0f -- " % sk, "%-40s" % kt, '%-40r' % kv, str(v)[:40], '...')
|
||||||
|
|
||||||
# Output all the attributes
|
# Output all the attributes
|
||||||
def attr_sort_key(a):
|
for _, ktype, _, kvalue, k, v in di:
|
||||||
k, v = a
|
indent_n = list(indent)+[k]
|
||||||
if " " in k:
|
|
||||||
ktype, kvalue = k.split(" ", 1)
|
|
||||||
else:
|
|
||||||
ktype = k
|
|
||||||
kvalue = ""
|
|
||||||
|
|
||||||
if ktype == "comp_attribute":
|
if ktype == 'define':
|
||||||
ktype = kvalue
|
for d in sorted(data['define'], key=lambda d: d['group_name']+'.'+d['attribute_name']):
|
||||||
kvalue = None
|
o.append('%sdefine(%s,%s,%s);' % (
|
||||||
|
INDENT*len(indent_n),
|
||||||
|
d['attribute_name'],
|
||||||
|
d['group_name'],
|
||||||
|
d['attribute_type']),
|
||||||
|
)
|
||||||
|
|
||||||
kn, ktype = liberty_sort(ktype)
|
elif ktype == "comp_attribute":
|
||||||
|
o.extend(liberty_composite(kvalue, v, indent_n))
|
||||||
return (kn, ktype, kvalue)
|
|
||||||
|
|
||||||
for k, v in sorted(data.items(), key=attr_sort_key):
|
|
||||||
|
|
||||||
if " " in k:
|
|
||||||
ktype, kvalue = k.split(" ", 1)
|
|
||||||
else:
|
|
||||||
ktype = k
|
|
||||||
kvalue = ""
|
|
||||||
|
|
||||||
if ktype == "comp_attribute":
|
|
||||||
o.extend(liberty_composite(kvalue, v, i_n))
|
|
||||||
|
|
||||||
elif isinstance(v, dict):
|
elif isinstance(v, dict):
|
||||||
assert isinstance(v, dict), (dtype, dvalue, k, v)
|
assert isinstance(v, dict), (dtype, dvalue, k, v)
|
||||||
o.extend(liberty_dict(ktype, kvalue, v, i_n))
|
o.extend(liberty_dict(ktype, kvalue, v, indent_n))
|
||||||
|
|
||||||
elif isinstance(v, list):
|
elif isinstance(v, list):
|
||||||
assert len(v) > 0, (dtype, dvalue, k, v)
|
assert len(v) > 0, (dtype, dvalue, k, v)
|
||||||
if isinstance(v[0], dict):
|
if isinstance(v[0], dict):
|
||||||
def k(o):
|
def sk(o):
|
||||||
return o.items()
|
return o.items()
|
||||||
|
|
||||||
for l in sorted(v, key=k):
|
for l in sorted(v, key=sk):
|
||||||
o.extend(liberty_dict(ktype, kvalue, l, i_n))
|
o.extend(liberty_dict(ktype, kvalue, l, indent_n))
|
||||||
|
|
||||||
elif is_liberty_list(k):
|
elif is_liberty_list(ktype):
|
||||||
o.extend(liberty_list(k, v, i_n))
|
o.extend(liberty_list(ktype, v, indent_n))
|
||||||
|
|
||||||
elif "clk_width" == k:
|
elif "clk_width" == ktype:
|
||||||
for l in sorted(v):
|
for l in sorted(v):
|
||||||
o.append("%s%s : %s;" % (INDENT*len(i_n), k, l))
|
o.append("%s%s : %s;" % (INDENT*len(indent_n), k, l))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown %s: %r\n%s" % (k, v, i_n))
|
raise ValueError("Unknown %s: %r\n%s" % (k, v, indent_n))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
v = '"%s"' % v
|
v = '"%s"' % v
|
||||||
elif isinstance(v, (float,int)):
|
elif isinstance(v, (float,int)):
|
||||||
v = liberty_float(v)
|
v = liberty_float(v)
|
||||||
o.append("%s%s : %s;" % (INDENT*len(i_n), k, v))
|
o.append("%s%s : %s;" % (INDENT*len(indent_n), k, v))
|
||||||
|
|
||||||
o.append("%s}" % (INDENT*len(i)))
|
o.append("%s}" % (INDENT*len(indent)))
|
||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
|
@ -693,5 +891,4 @@ def main():
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import doctest
|
import doctest
|
||||||
doctest.testmod()
|
doctest.testmod()
|
||||||
|
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|
|
@ -21,6 +21,7 @@ import dataclasses
|
||||||
import dataclasses_json
|
import dataclasses_json
|
||||||
import functools
|
import functools
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
@ -229,6 +230,64 @@ class OrderedFlag(Flag):
|
||||||
return hash(self._name_)
|
return hash(self._name_)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_numbers(s):
|
||||||
|
"""Create tuple with sequences of numbers converted to ints.
|
||||||
|
|
||||||
|
>>> extract_numbers("pwr_template13x10")
|
||||||
|
('pwr_template', 13, 'x', 10)
|
||||||
|
>>> extract_numbers("vio_10_10_1")
|
||||||
|
('vio_', 10, '_', 10, '_', 1)
|
||||||
|
"""
|
||||||
|
bits = []
|
||||||
|
for m in re.finditer("([^0-9]*)([0-9]*)", s):
|
||||||
|
if m.group(1):
|
||||||
|
bits.append(m.group(1))
|
||||||
|
if m.group(2):
|
||||||
|
bits.append(int(m.group(2)))
|
||||||
|
return tuple(bits)
|
||||||
|
|
||||||
|
|
||||||
|
def sortable_extracted_numbers(s):
|
||||||
|
"""Create output which is sortable by numeric values in string.
|
||||||
|
|
||||||
|
>>> sortable_extracted_numbers("pwr_template13x10")
|
||||||
|
('pwr_template', '0000000013', 'x', '0000000010')
|
||||||
|
>>> sortable_extracted_numbers("vio_10_10_1")
|
||||||
|
('vio_', '0000000010', '_', '0000000010', '_', '0000000001')
|
||||||
|
|
||||||
|
>>> l = ['a1', 'a2b2', 'a10b10', 'b2', 'a8b50', 'a10b1']
|
||||||
|
>>> l.sort()
|
||||||
|
>>> print('\\n'.join(l))
|
||||||
|
a1
|
||||||
|
a10b1
|
||||||
|
a10b10
|
||||||
|
a2b2
|
||||||
|
a8b50
|
||||||
|
b2
|
||||||
|
>>> l.sort(key=sortable_extracted_numbers)
|
||||||
|
>>> print('\\n'.join(l))
|
||||||
|
a1
|
||||||
|
a2b2
|
||||||
|
a8b50
|
||||||
|
a10b1
|
||||||
|
a10b10
|
||||||
|
b2
|
||||||
|
|
||||||
|
"""
|
||||||
|
zero_pad_str = '%010i'
|
||||||
|
bits = extract_numbers(s)
|
||||||
|
o = []
|
||||||
|
|
||||||
|
for b in bits:
|
||||||
|
if not isinstance(b, str):
|
||||||
|
assert isinstance(b, int), (b, bits)
|
||||||
|
assert len(str(b)) < len(zero_pad_str % 0)
|
||||||
|
b = zero_pad_str % b
|
||||||
|
o.append(b)
|
||||||
|
return tuple(o)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import doctest
|
import doctest
|
||||||
doctest.testmod()
|
doctest.testmod()
|
||||||
|
|
Loading…
Reference in New Issue