Source code for pdkmaster.io.parsing.tf

# SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-or-later OR CERN-OHL-S-2.0+ OR Apache-2.0
from ... import _util
from .skill_grammar import SkillFile, _skill_functionlist


__all__ = ["TechFile"]


#
# Technology file support functions
#
def _get_layername(v):
    """Get layer name. Value can be a string or a list of two values for layer purpose
    If purpose is specified it will return a layer name using '.' as delimiter between layername
    and purpose
    """
    if isinstance(v, str):
        return _util.strip_literal(v)
    elif len(v) == 1:
        return _util.strip_literal(v[0])
    elif len(v) == 2:
        return _util.strip_literal(v[0])+"."+_util.strip_literal(v[1])
    else:
        raise ValueError("{!r} is not a valid layer specification")


def _get_bool(v):
    assert type(v) is bool
    return v


def _get_combinedlayername(v):
    """Returns a combination of layers, the layers will be separated by a ':' as delimiter.

    This is used for layer pair for design rules or for a triple of layers for via rule"""
    assert len(v) > 1
    return ":".join([_get_layername(elem) for elem in v])


def _get_numornil(v):
    if isinstance(v, bool):
        assert not v
        return None
    else:
        assert isinstance(v, (int, float))
        return v


def _prop_value(elems, **kwargs):
    ret = {}
    for v in elems:
        assert 2 <= len(v) <= 4
        prop = _util.strip_literal(v[0])
        value = v[1]
        ret[prop] = value
        if len(v) > 2:
            prop += "."+v[2]
            value = v[3] if len(v) > 3 else True
            ret[prop] = value

    return ret


def _prop_layers_value_optextra(elems, **kwargs):
    """data: [prop (layer1 (layer2 (layer3))) value (extraprop (extravalue))]
    if no layer is present "_" will be used for layername"""
    ret = {}
    for v in elems:
        cond = v[0]
        if len(v) == 2:
            layer = "_"
            spec = v[1]
            extra = None
        else:
            end = 2
            while (
                ((type(v[end]) is str) and v[end][0] != "'")
                or ((type(v[end]) is list) and (type(v[end][0]) is str))
            ):
                end += 1
            if end == 2:
                layer = _get_layername(v[1])
            else:
                layer = _get_combinedlayername(v[1:end])
            spec = v[end]
            if len(v) > end+1:
                extra = cond + "." + v[end+1]
                extra_spec = v[end+2] if len(v) > end+2 else True
            else:
                extra = None
        if layer in ret:
            ret[layer][cond] = spec
        else:
            ret[layer] = {cond: spec}
        if extra:
            ret[layer][extra] = extra_spec

    return ret


def _prop_cumulative_value(elems, *, functionname, **kwargs):
    d = {}
    for v in elems:
        cond = v[0]
        if functionname == "cumulativeMetalAntenna":
            cond += ".cumulative_metal"
        elif functionname == "cumulativeViaAntenna":
            cond += ".cumulative_via"
        else:
            raise Exception(f"unhandled name '{functionname}'")
        d[cond] = v[1]
        if len(v) > 2:
            d[cond + "." + v[2]] = True if len(v) == 3 else v[3]

    return {"_": d}


def _prop_value_units(elems, **kwargs):
    ret = {}
    for prop, units, value in elems:
        ret[prop] = [value, units]

    return ret


def _layer_value(elems, *, unique=False, **kwargs):
    ret = {}
    for layer, value in elems:
        layername = _get_layername(layer)
        if unique:
            assert layername not in ret
        ret[layername] = value

    return ret


def _layer_values(elems, **kwargs):
    ret = {}
    for v in elems:
        ret[_get_layername(v[0])] = v[1:]

    return ret


def _layers(elems, **kwargs):
    return [_get_layername(elem) for elem in elems]


def _combinedlayers(elems, **kwargs):
    return [_get_combinedlayername(elem) for elem in elems]


def _techParams(elems, **kwargs):
    ret = {}
    for v in elems:
        assert len(v) == 2
        paramname = v[0]
        if paramname.endswith("layer") | paramname.endswith("Layer"):
            ret[paramname] = _get_layername(v[1])
        elif paramname.endswith("layers"):
            if not isinstance(v[1], bool):
                ret[paramname] = [_get_layername(layer) for layer in v[1]]
            else:
                assert not v[1]
        else:
            ret[v[0]] = v[1]

    return ret


def _name_abbreviation(elems, **kwargs):
    ret = {}
    for v in elems:
        assert len(v) in (2, 3)
        techname = v[0]
        d = {"number": v[1]}
        if len(v) > 2:
            d["abbreviation"] = v[2]
        ret[techname] = d

    return ret


def _techDisplays(elems, **kwargs):
    ret = {}
    for v in elems:
        layername = _get_layername(v[0:2])
        ret[layername] = {
            "packet": v[2],
            "visible": _get_bool(v[3]),
            "selectable": _get_bool(v[4]),
            "con2chgly": _get_bool(v[5]),
            "drawable": _get_bool(v[6]),
            "valid": _get_bool(v[7]),
        }

    return ret


def _standardViaDefs(elems, **kwargs):
    ret = []
    for v in elems:
        d = {
            "name": v[0],
            "layer1": {
                "name": _get_layername(v[1]),
                "enclosure": v[5],
                "offset": v[7],
            },
            "via": {
                "name": _get_layername(v[3][0]),
                "width": v[3][1],
                "height": v[3][2],
                "rows": v[4][0],
                "cols": v[4][1],
            },
            "layer2": {
                "name": _get_layername(v[2]),
                "enclosure": v[6],
                "offset": v[8],
            },
            "offset": v[9],
        }
        if len(v[3]) > 3:
            d["via"]["resistance"] = v[3][3]
        if len(v[4]) > 2:
            d["via"]["space"] = v[4][2]
        if len(v[4]) > 3:
            d["via"]["pattern"] = v[4][3]
        if (len(v) > 10) and v[10]:
            d["layer1"]["implant"] = {
                "name": _get_layername(v[10]),
                "enclosure": v[11],
            }
        if len(v) > 12:
            d["layer2"]["implant"] = {
                "name": _get_layername(v[12]),
                "enclosure": v[13],
            }
            if len(v) > 14:
                d["well"] = v[14]

        ret.append(d)

    return ret


def _customViaDefs(elems, **kwargs):
    ret = {}
    for v in elems:
        vianame = v[0]
        assert vianame not in ret
        ret[vianame] ={
            "library": v[1],
            "cell": v[2],
            "view": v[3],
            "layer1": _get_layername(v[4]),
            "layer2": _get_layername(v[5]),
            "resistance": v[6],
        }

    return ret


def _spacingTables(elems, **kwargs):
    ret = {}
    for v in elems:
        if isinstance(v[2], list):
            layer = _get_layername(v[1])
            define = v[2]
            table = v[3]
            tail = v[4:]
        elif len(v) > 4:
            layer = _get_combinedlayername(v[1:3])
            define = v[3]
            table = v[4]
            tail = v[5:]
        else:
            raise ValueError("Unexpected spacingTables length")
        assert len(define[0]) == 3 or len(define[0]) == 6
        assert not define[0][1] and not define[0][2]

        d = {
            "index": define[0][0],
        }

        if len(define[0]) > 3:
            assert not define[0][4] and not define[0][5]
            d["index2"] = define[0][3]
        if len(define) > 1:
            d["default"] = define[1]
        if len(define[0]) == 3:
            d["table"] = [[table[2*i], table[2*i+1]] for i in range(len(table)//2)]
        else: # len(define[0]) == 6
            d["table"] = [[*table[2*i], table[2*i+1]] for i in range(len(table)//2)]

        if len(tail) == 1:
            d[tail[0]] = True
        elif len(tail) > 1:
            d[tail[0]] = tail[1]

        if layer in ret:
            ret[layer][v[0]] = d
        else:
            ret[layer] = {v[0]: d}

    return ret


def _viaSpecs(elems, **kwargs):
    ret = {}
    for v in elems:
        layer = _get_combinedlayername(v[0:2])
        if len(v[2]) == 1:
            vias = v[2][0]
        else:
            vias = v[2]
        ret[layer] = vias

    return ret


def _antennaModels(elems, **kwargs):
    ret = {}
    for v in elems:
        modelname = v[0]
        rules = {}
        for rule in v[1:]:
            for rulename, spec in rule.items():
                if rulename == "antenna":
                    v2 = _prop_layers_value_optextra(spec, functionname=rulename)
                elif rulename in ("cumulativeMetalAntenna", "cumulativeViaAntenna"):
                    v2 =  _prop_cumulative_value(spec, functionname=rulename)
                else:
                    raise ValueError(f"Unsupported rulename '{rulename}' for antenneModels")
                for layer, layerspec in v2.items():
                    if layer in rules:
                        rules[layer].update(layerspec)
                    else:
                        rules[layer] = layerspec
        ret[modelname] = rules

    return ret


def _constraintGroups(elems, **kwargs):
    ret = {}
    for v in elems:
        groupname = _util.strip_literal(v[0])
        override = _get_bool(v[1])
        constraints = {"override": override}
        if (len(v) > 2) and isinstance(v[2], str):
            constraints["abbreviation"] = v[2]
            start = 3
        else:
            start = 2
        for constraint in v[start:]:
            for rulename, rules in constraint.items():
                if rulename in constraints:
                    c2 = constraints[rulename]
                    def add2layer(rule):
                        layer, spec = rule
                        if layer in c2:
                            c2[layer].update(spec)
                            return True
                        else:
                            return False
                    c2.update(filter(
                        lambda r: not add2layer(r), rules.items(),
                    ))
                else:
                    constraints[rulename] = rules

        # Convert spacings, orderedSpacings, spacingTables, routingGrids, antennaModels -> rules
        layerrules = constraints.pop("spacings", {})

        # Add specified groups, optionally transforming the rule name
        def _add_table(name):
            if name.endswith(".ref"):
                name[-4:] = ".table.ref"
            else:
                name += ".table"
            return name
        group_spec = [
            ("orderedSpacings", lambda name: name),
            ("electrical", lambda name: name),
            ("spacingTables", _add_table),
            ("routingGrids", lambda name: "routing."+name),
        ]
        for subgroupname, nametrans in group_spec:
            for layer, addrules in constraints.pop(subgroupname, {}).items():
                # Remove minEnclosure if exist minOppExtension with same minimal enclosure
                if ((subgroupname == "orderedSpacings") and ("minEnclosure" in addrules) and ("minOppExtension" in addrules)):
                    if addrules["minEnclosure"] == addrules["minOppExtension"][0]:
                        addrules.pop("minEnclosure")
                rules = layerrules.get(layer, {})
                rules.update({nametrans(rulename): data for rulename, data in addrules.items()})
                layerrules[layer] = rules

        # Add antennaModels -> rules; append antenna model name if not default
        for modelname, models in constraints.pop("antennaModels", {}).items():
            modelsuffix = "" if modelname == "default" else "."+modelname
                
            for layer, arules in models.items():
                rules = layerrules.get(layer, {})
                rules.update({rulename+modelsuffix: data for rulename, data in arules.items()})
                layerrules[layer] = rules

        if layerrules:
            constraints["rules"] = layerrules
        
        ret[groupname] = constraints

    return ret


def _techDerivedLayers(elems, **kwargs):
    ret = {}
    for v in elems:
        assert len(v) == 3 and len(v[2]) == 3
        if isinstance(v[2][2], (int, float)):
            ret[v[0]] = {
                "number": v[1],
                "layer": _get_layername(v[2][0]),
                v[2][1]: v[2][2],
            }
        else:
            ret[v[0]] = {
                "number": v[1],
                "layer": _get_combinedlayername([v[2][0], v[2][2]]),
                "operation": v[2][1],
            }

    return ret


def _functions(elems, **kwargs):
    ret = {}
    for v in elems:
        assert 2 <= len(v) <= 3
        layer = _get_layername(v[0])
        assert layer not in ret
        ret[layer] = {"function": _util.strip_literal(v[1])}
        if len(v) > 2:
            ret[layer]["mask_number"] = v[2]

    return ret


def _multipartPathTemplates(elems, **kwargs):
    ret = {}
    for v in elems:
        assert len(v) == 5

        pathname = v[0]

        v2 = v[1]
        l = len(v2)
        assert 1 <= l <= 9
        d = {"layer": _get_layername(v2[0])}
        if l > 1:
            d["width"] = v2[1]
        if l > 2:
            d["choppable"] = _get_bool(v2[2])
        if l > 3:
            d["endtype"] = v2[3]
        if l > 4:
            d["begin_extension"] = v2[4]
        if l > 5:
            d["end_extension"] = v2[5]
        if l > 6:
            d["justify"] = _get_bool(v2[6])
        if l > 7:
            d["offset"] = v2[7]
        if l > 8:
            d["connectivity"] = v2[8]
        spec = {"master": d}

        v2 = v[2]
        if not isinstance(v2, bool):
            subpaths = []
            for v3 in v2:
                l = len(v3)
                assert 1 <= l <= 8
                d = {"layer": _get_layername(v3[0])}
                if l > 1:
                    d["width"] = v3[1]
                if l > 2:
                    d["choppable"] = _get_bool(v3[2])
                if l > 3:
                    d["separation"] = v3[3]
                if l > 4:
                    d["justification"] = v3[4]
                if l > 5:
                    d["begin_offset"] = v3[5]
                if l > 6:
                    d["end_offset"] = v3[6]
                if l > 7:
                    d["connectivity"] = v3[7]
                subpaths.append(d)
                spec["offset"] = subpaths
        else:
            assert not v2

        v2 = v[3]
        if not isinstance(v2, bool):
            subpaths = []
            for v3 in v2:
                l = len(v3)
                assert 1 <= l <= 7
                d = {"layer": _get_layername(v3[0])}
                if l > 1:
                    d["enclosure"] = v3[1]
                if l > 2:
                    d["choppable"] = _get_bool(v3[2])
                if l > 3:
                    d["separation"] = v3[3]
                if l > 4:
                    d["begin_offset"] = v3[4]
                if l > 5:
                    d["end_offset"] = v3[5]
                if l > 6:
                    d["connectivity"] = v3[6]
                subpaths.append(d)
            spec["enclosure"] = subpaths
        else:
            assert not v2

        v2 = v[4]
        if not isinstance(v2, bool):
            subpaths = []
            for v3 in v2:
                l = len(v3)
                assert 1 <= l <= 13
                d = {"layer": _get_layername(v3[0])}
                if l > 1:
                    n = _get_numornil(v3[1])
                    if n is not None:
                        d["width"] = n
                if l > 2:
                    n = _get_numornil(v3[2])
                    if n is not None:
                        d["length"] = n
                if l > 3:
                    d["choppable"] = _get_bool(v3[3])
                if l > 4:
                    d["separation"] = v3[4]
                if l > 5:
                    d["justification"] = v3[5]
                if l > 6:
                    n = _get_numornil(v3[6])
                    if n is not None:
                        d["space"] = n
                if l > 7:
                    d["begin_offset"] = v3[7]
                if l > 8:
                    d["end_offset"] = v3[8]
                if l > 9:
                    d["gap"] = v3[9]
                if l > 10:
                    c = v3[10]
                    if not (isinstance(c, bool) and not c):
                        d["connectivity"] = c
                if l > 11:
                    n = _get_numornil(v3[11])
                    if n is not None:
                        d["begin_segoffset"] = n
                if l > 12:
                    n = _get_numornil(v3[12])
                    if n is not None:
                        d["end_segoffset"] = n
                subpaths.append(d)
            spec["rects"] = subpaths

        ret[pathname] = spec

    return ret


def _streamLayers(elems, **kwargs):
    ret = {}
    for layer, number, datatype, translate in elems:
        layername = _get_layername(layer)
        ret[layername] = {
            "number": number,
            "datatype": datatype,
            "translate": _get_bool(translate),
        }

    return ret


def _layerFunctions(elems, **kwargs):
    ret = {}
    for v in elems:
        assert 2 <= len(v) <= 3
        layername = _get_layername(v[0])
        d = {"function": v[1]}
        if len(v) > 2:
            d["masknumber"] = v[2]
        assert layername not in ret
        ret[layername] = d

    return ret


def _spacingRules(elems, **kwargs):
    ret = {}
    for v in elems:
        specname = v[0]
        assert 3 <= len(v) <= 4
        if len(v) == 3:
            layername = _get_layername(v[1])
        elif len(v) == 4:
            layername = _get_combinedlayername(v[1:3])
        specvalue = v[-1]
        if layername in ret:
            ret[layername][specname] = specvalue
        else:
            ret[layername] = {specname: specvalue}

    return ret


def _layerDefinitions(elems, **kwargs):
    class _LookupExtend():
        def __init__(self, data):
            self.data = data
            self.newidx = -1
        
        def __getitem__(self, item):
            try:
                return self.data[item]
            except KeyError:
                self.data[item] = value = {"number": self.newidx}
                self.newidx -= 1
                return value

    d_elems = {}
    for elem in elems:
        assert isinstance(elem, dict) and len(elem) == 1
        d_elems.update(elem)

    techlayers = _LookupExtend(d_elems.pop("techLayers"))
    techpurposes = _LookupExtend(d_elems.pop("techPurposes"))
    techlayerpurposes = d_elems.pop("techLayerPurposePriorities")
    techdisplays = d_elems.pop("techDisplays")
    techlayerproperties = d_elems.pop("techLayerProperties", {})
    assert set(techlayerpurposes) == set(techdisplays.keys())

    def _layerpurpose2value(name):
        lname, pname = name.split('.')
        ldata = techlayers[lname]
        labbr = ldata.get("abbreviation")
        if labbr == lname:
            labbr = None
        pdata = techpurposes[pname]
        pabbr = pdata.get("abbreviation")
        if pabbr == pname:
            pabbr = None

        aliases = []
        if pname == "drawing":
            aliases.append(lname)
        if labbr and (labbr != lname):
            aliases.append(".".join((labbr, pname)))
            if pabbr and (pabbr != pname):
                aliases.append(".".join((labbr, pabbr)))
        if pabbr and (pabbr != pname):
            aliases.append(".".join((lname, pabbr)))

        value = {
            "name": name,
            "aliases": aliases,
            "layer": ldata["number"],
            "purpose": pdata["number"],
        }
        value.update(techdisplays[name])
        for name_it in [name] + aliases:
            value.update(techlayerproperties.pop(name_it, {}))
        return value

    d_elems["layers"] = [_layerpurpose2value(layerpurpose) for layerpurpose in techlayerpurposes]
    assert len(techlayerproperties) == 0, f"Remaining layerproperties: {list(techlayerproperties.keys())}"
    return d_elems


_value4function_table = {
    "techLayerPurposePriorities": _layers,
    "controls": _skill_functionlist,
    "interconnect": _prop_value,
    "viewTypeUnits": _prop_value_units,
    "viaLayers": _combinedlayers,
    "routingGrids": _prop_layers_value_optextra,
    "spacings": _prop_layers_value_optextra,
    "orderedSpacings": _prop_layers_value_optextra,
    "electrical": _prop_layers_value_optextra,
    "orderedElectrical": _prop_layers_value_optextra,
    "techLayerProperties": _prop_layers_value_optextra,
    "characterizationRules": _prop_layers_value_optextra,
    "currentDensity": _prop_layers_value_optextra,
    "routingDirections": _layer_value,
    "stampLabelLayers": _layer_values,
    "compactorLayers": (_layer_value, {"unique": True}),
    "techPurposes": _name_abbreviation,
    "techLayers": _name_abbreviation,
    "techParams": _techParams,
    "techDisplays": _techDisplays,
    "standardViaDefs": _standardViaDefs,
    "customViaDefs": _customViaDefs,
    "viaDefs": _skill_functionlist,
    "spacingTables": _spacingTables,
    "viaSpecs": _viaSpecs,
    "antennaModels": _antennaModels,
    "constraintGroups": _constraintGroups,
    "techDerivedLayers": _techDerivedLayers,
    "functions": _functions,
    "multipartPathTemplates": _multipartPathTemplates,
    "streamLayers": _streamLayers,
    "layerFunctions": _layerFunctions,
    "spacingRules": _spacingRules,
    "orderedSpacingRules": _spacingRules,
    "layerRules": _skill_functionlist,
    "layerDefinitions": _layerDefinitions,
    # Following functions are not directly converted but done inside antennaModels function
    # "antenna": _prop_layers_value_optextra,
    # "cumulativeMetalAntenna": _prop_cumulative_value,
    # "cumulativeViaAntenna": _prop_cumulative_value,
}


#
# Grammar
#
[docs]class TechFile(SkillFile):
[docs] def grammar_elem_init(self, sessiondata): super().grammar_elem_init(sessiondata) self.ast = {"TechFile": self.ast["SkillFile"]} newvalue = {} layerrules = {} for v in self.value["SkillFile"]: if ("layerDefinitions" in v) and ("layers" in v["layerDefinitions"]): layers = v["layerDefinitions"].pop("layers") layers_idx = {layer["name"]: layer for layer in layers} for alias_idx in [{alias: layer for alias in layer["aliases"]} for layer in layers]: layers_idx.update(alias_idx) newvalue["layers"] = { "list": layers, } # Add remaining fields if present if v["layerDefinitions"]: newvalue.update(v) elif ("layerRules" in v): assert len(v) == 1 layerrules = v["layerRules"] else: newvalue.update(v) # Process layerrules # Filter out empty fields layerrules = dict(filter(lambda item: item[1], layerrules.items())) if "equivalentLayers" in layerrules: newvalue["layers"]["equivalent"] = layerrules.pop("equivalentLayers") for layername, fdata in layerrules.pop("functions", {}).items(): layers_idx[layername].update(fdata) for layername, direction in layerrules.pop("routingDirections", {}).items(): layers_idx[layername]["direction"] = direction if layerrules: newvalue.update({"layerRules": layerrules}) # Add connects section based on via section try: defs = newvalue["viaDefs"]["standardViaDefs"] except KeyError: pass else: newvalue["connects"] = list({ "{}:{}:{}".format( viadef["layer1"]["name"], viadef["via"]["name"], viadef["layer2"]["name"] ) for viadef in defs }) self.value = newvalue
[docs] @classmethod def parse_string(cls, text): return super().parse_string( text, value4funcs=_value4function_table, dont_convert=["currentDensity"] )