diff --git a/scripts/python-skywater-pdk/skywater_pdk/liberty.py b/scripts/python-skywater-pdk/skywater_pdk/liberty.py index 0e87e61..89f561b 100755 --- a/scripts/python-skywater-pdk/skywater_pdk/liberty.py +++ b/scripts/python-skywater-pdk/skywater_pdk/liberty.py @@ -32,6 +32,8 @@ from collections import defaultdict from typing import Tuple, List, Dict from . import sizes +from .utils import sortable_extracted_numbers + debug = False @@ -346,30 +348,220 @@ def generate(library_dir, lib, corner, ocorner_type, icorner_type, cells): 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_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") - (1, 'variable') - >>> liberty_sort("index_3") - (3, 'index') - >>> liberty_sort("values") # doctest: +ELLIPSIS - (inf, 'values') + 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 + """ - m = RE_LIBERTY_LIST.match(k) + assert ':' not in attr_name, attr_name + + m = RE_LIBERTY_LIST.match(attr_name) if m: k, n = m.group(1), m.group(2) - n = int(n) - else: - n = float('inf') - return n, k + + 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): @@ -426,6 +618,9 @@ def liberty_float(f): return s +INDENT=" " + + def liberty_composite(k, v, i=tuple()): """ @@ -522,88 +717,91 @@ def liberty_list(k, v, i=tuple()): 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) o = [] + if dvalue: dbits = dvalue.split(",") for j, d in enumerate(dbits): if '"' in d: - assert d.startswith('"'), (dvalue, dbits, i) - assert d.endswith('"'), (dvalue, dbits, i) + 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(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 'define' in data: - for d in sorted(data['define'], key=lambda d: d['group_name']+'.'+d['attribute_name']): - o.append('%sdefine(%s,%s,%s);' % (INDENT*len(i_n), d['attribute_name'], d['group_name'], d['attribute_type'])) - o.append('') + if ktype == "comp_attribute": + sortable_kt = liberty_attribute_order(kvalue) + else: + sortable_kt = liberty_attribute_order(ktype) - 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 - def attr_sort_key(a): - k, v = a - if " " in k: - ktype, kvalue = k.split(" ", 1) - else: - ktype = k - kvalue = "" + for _, ktype, _, kvalue, k, v in di: + indent_n = list(indent)+[k] - if ktype == "comp_attribute": - ktype = kvalue - kvalue = None + if ktype == 'define': + for d in sorted(data['define'], key=lambda d: d['group_name']+'.'+d['attribute_name']): + o.append('%sdefine(%s,%s,%s);' % ( + INDENT*len(indent_n), + d['attribute_name'], + d['group_name'], + d['attribute_type']), + ) - kn, ktype = liberty_sort(ktype) - - 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 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, i_n)) + o.extend(liberty_dict(ktype, kvalue, v, indent_n)) elif isinstance(v, list): assert len(v) > 0, (dtype, dvalue, k, v) if isinstance(v[0], dict): - def k(o): + def sk(o): return o.items() - for l in sorted(v, key=k): - o.extend(liberty_dict(ktype, kvalue, l, i_n)) + for l in sorted(v, key=sk): + o.extend(liberty_dict(ktype, kvalue, l, indent_n)) - elif is_liberty_list(k): - o.extend(liberty_list(k, v, i_n)) + elif is_liberty_list(ktype): + o.extend(liberty_list(ktype, v, indent_n)) - elif "clk_width" == k: + elif "clk_width" == ktype: 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: - raise ValueError("Unknown %s: %r\n%s" % (k, v, i_n)) + raise ValueError("Unknown %s: %r\n%s" % (k, v, indent_n)) else: if isinstance(v, str): v = '"%s"' % v elif isinstance(v, (float,int)): 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 @@ -693,5 +891,4 @@ def main(): if __name__ == "__main__": import doctest doctest.testmod() - sys.exit(main()) diff --git a/scripts/python-skywater-pdk/skywater_pdk/utils.py b/scripts/python-skywater-pdk/skywater_pdk/utils.py index 9f7a641..30b3d9d 100644 --- a/scripts/python-skywater-pdk/skywater_pdk/utils.py +++ b/scripts/python-skywater-pdk/skywater_pdk/utils.py @@ -21,6 +21,7 @@ import dataclasses import dataclasses_json import functools import random +import re import sys from dataclasses import dataclass @@ -229,6 +230,64 @@ class OrderedFlag(Flag): 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__": import doctest doctest.testmod()