| #!/usr/bin/env python3 | |
| """ | |
| A number of utility functions useful for traversing pb_type hierarchy as | |
| defined in VPR architecture XML file. | |
| """ | |
| import re | |
| import lxml.etree as ET | |
| # ============================================================================= | |
| def is_leaf_pbtype(xml_pbtype): | |
| """ | |
| Returns true when the given pb_type is a leaf. | |
| """ | |
| assert xml_pbtype.tag == "pb_type", xml_pbtype.tag | |
| return "blif_model" in xml_pbtype.attrib | |
| def get_parent_pb(xml_pbtype): | |
| """ | |
| Returns a parent pb_type of the given one or none if it is a top-level | |
| complex block | |
| """ | |
| assert xml_pbtype.tag in [ | |
| "pb_type", "mode", "interconnect" | |
| ], xml_pbtype.tag | |
| # Get immediate parent | |
| xml_parent = xml_pbtype.getparent() | |
| # We've hit the end | |
| if xml_parent is None or xml_parent.tag == "complexblocklist": | |
| return None | |
| # The immediate parent is a mode, jump one more level up | |
| if xml_parent is not None and xml_parent.tag == "mode": | |
| xml_parent = xml_parent.getparent() | |
| return xml_parent | |
| def get_parent_pb_and_mode(xml_pbtype): | |
| """ | |
| Returns a parent pb_type and mode element for the given pb_type. If there | |
| are no modes (implicit default mode) then the mode element returned is | |
| the same as the pb_type element. | |
| """ | |
| assert xml_pbtype.tag in ["pb_type", "mode"], xml_pbtype.tag | |
| # Get immediate parent | |
| xml_parent = xml_pbtype.getparent() | |
| # We've hit the end | |
| if xml_parent is None or xml_parent.tag == "complexblocklist": | |
| return None, None | |
| assert xml_parent.tag in ["pb_type", "mode"], xml_parent.tag | |
| # pb_type parent | |
| if xml_pbtype.tag == "pb_type": | |
| if xml_parent.tag == "pb_type": | |
| return xml_parent, xml_parent | |
| elif xml_parent.tag == "mode": | |
| return xml_parent.getparent(), xml_parent | |
| elif xml_pbtype.tag == "mode": | |
| return xml_parent.getparent(), xml_pbtype | |
| def get_pb_by_name(xml_parent, name): | |
| """ | |
| Searches for a pb_type with the given name inside a parent pb_type. | |
| Returns either a child or the parent. | |
| The provided parent may be a mode but a pb_type is always returned. | |
| """ | |
| assert xml_parent.tag in ["pb_type", "mode"], xml_parent.tag | |
| # Check the parent name. If this is a mode then check its parent name | |
| if xml_parent.tag == "mode": | |
| xml_parent_pb = get_parent_pb(xml_parent) | |
| if xml_parent_pb.attrib["name"] == name: | |
| return xml_parent_pb | |
| else: | |
| if xml_parent.attrib["name"] == name: | |
| return xml_parent | |
| # Check children | |
| for xml_pbtype in xml_parent.findall("pb_type"): | |
| if xml_pbtype.attrib["name"] == name: | |
| return xml_pbtype | |
| # None found | |
| return None | |
| # ============================================================================= | |
| def yield_pb_children(xml_parent): | |
| """ | |
| Yields all child pb_types and their indices taking into account num_pb. | |
| """ | |
| for xml_child in xml_parent.findall("pb_type"): | |
| num_pb = int(xml_child.get("num_pb", 1)) | |
| for i in range(num_pb): | |
| yield xml_child, i | |
| # ============================================================================= | |
| INTERCONNECT_PORT_SPEC_RE = re.compile( | |
| r"((?P<pbtype>[A-Za-z0-9_]+)(\[(?P<indices>[0-9:]+)\])?\.)" | |
| r"(?P<port>[A-Za-z0-9_]+)(\[(?P<bits>[0-9:]+)\])?" | |
| ) | |
| def get_pb_and_port(xml_ic, port_spec): | |
| """ | |
| """ | |
| assert xml_ic.tag == "interconnect" | |
| # Match the port name | |
| match = INTERCONNECT_PORT_SPEC_RE.fullmatch(port_spec) | |
| assert match is not None, port_spec | |
| # Get the referenced pb_type | |
| xml_parent = xml_ic.getparent() | |
| xml_pbtype = get_pb_by_name(xml_parent, match.group("pbtype")) | |
| assert xml_pbtype is not None, port_spec | |
| # Find the port | |
| port = match.group("port") | |
| for xml_port in xml_pbtype: | |
| if xml_port.tag in ["input", "output", "clock"]: | |
| # Got it | |
| if xml_port.attrib["name"] == port: | |
| return xml_pbtype, xml_port | |
| else: | |
| assert False, (port_spec, xml_pbtype.attrib["name"], port) | |
| def yield_indices(index_spec): | |
| """ | |
| Given an index specification string as "<i1>:<i0>" or "<i>" yields all bits | |
| that it is comprised of. The function also supports cases when i0 > i1. | |
| """ | |
| # None | |
| if index_spec is None: | |
| return | |
| # Range | |
| elif ":" in index_spec: | |
| i0, i1 = [int(i) for i in index_spec.split(":")] | |
| if i0 > i1: | |
| for i in range(i1, i0 + 1): | |
| yield i | |
| elif i0 < i1: | |
| for i in range(i0, i1 + 1): | |
| yield i | |
| else: | |
| yield i0 | |
| # Single value | |
| else: | |
| yield int(index_spec) | |
| def yield_pins(xml_ic, port_spec, skip_index=True): | |
| """ | |
| Yields individual port pins as "<pb_type>[<pb_index].<port>[<bit>]" given | |
| the port specification. | |
| """ | |
| assert xml_ic.tag == "interconnect" | |
| # Match the port name | |
| match = INTERCONNECT_PORT_SPEC_RE.fullmatch(port_spec) | |
| assert match is not None, port_spec | |
| # Get the referenced pb_type | |
| xml_parent = xml_ic.getparent() | |
| xml_pbtype = get_pb_by_name(xml_parent, match.group("pbtype")) | |
| assert xml_pbtype is not None, port_spec | |
| # Find the port | |
| port = match.group("port") | |
| for xml_port in xml_pbtype: | |
| if xml_port.tag in ["input", "output", "clock"]: | |
| if xml_port.attrib["name"] == port: | |
| width = int(xml_port.attrib["num_pins"]) | |
| break | |
| else: | |
| assert False, (port_spec, xml_pbtype.attrib["name"], port) | |
| # Check if we are referencing an upstream parent | |
| is_parent_port = get_parent_pb(xml_ic) == xml_pbtype | |
| # Build a list of pb_type indices | |
| if not is_parent_port: | |
| num_pb = int(xml_pbtype.get("num_pb", 1)) | |
| indices = list(yield_indices(match.group("indices"))) | |
| if not indices: | |
| if num_pb > 1: | |
| indices = list(range(0, num_pb)) | |
| else: | |
| indices = [None] | |
| else: | |
| indices = [None] | |
| # Build a list of bit indices | |
| bits = list(yield_indices(match.group("bits"))) | |
| if not bits: | |
| if width > 1: | |
| bits = list(range(0, width)) | |
| else: | |
| bits = [None] | |
| # Yield individual pin names | |
| for i in indices: | |
| for j in bits: | |
| name = match.group("pbtype") | |
| if i is not None: | |
| name += "[{}]".format(i) | |
| elif not skip_index: | |
| name += "[0]" | |
| name += "." + match.group("port") | |
| if j is not None: | |
| name += "[{}]".format(j) | |
| elif not skip_index: | |
| name += "[0]" | |
| yield name | |
| # ============================================================================= | |
| def append_metadata(xml_item, meta_name, meta_data): | |
| """ | |
| Appends metadata to an element of architecture | |
| """ | |
| assert xml_item.tag in ["pb_type", "mode", "direct", "mux"] | |
| xml_meta = ET.Element("meta", {"name": meta_name}) | |
| xml_meta.text = meta_data | |
| # Use existing metadata section. If not present then create a new one | |
| xml_metadata = xml_item.find("metadata") | |
| if xml_metadata is None: | |
| xml_metadata = ET.Element("metadata") | |
| # Append meta, check for conflict | |
| for item in xml_metadata.findall("meta"): | |
| assert item.attrib["name"] != xml_meta.attrib["name"] | |
| xml_metadata.append(xml_meta) | |
| # Append | |
| if xml_metadata.getparent() is None: | |
| xml_item.append(xml_metadata) |