Пример #1
0
        def get_node_path(pin):

            # Split parts
            parts = pin.split(".")
            assert len(parts) == 2, pin

            # Parse the pb_type referred by the pin
            pin_pbtype = PathNode.from_string(parts[0])

            # This pin refers to the parent
            if pin_pbtype.name == parent_name:
                ref_pbtype = PathNode.from_string(path_parts[-1])

                # Fixup pb_type reference name
                part = "{}[{}]".format(
                    pin_pbtype.name,
                    ref_pbtype.index,
                )
                parts = path_parts[:-1] + [part] + parts[1:]

            else:
                parts = path_parts + parts

            # Assemble the full path
            node_path = ".".join(parts)
            return node_path
Пример #2
0
    def walk(block, path):
        """
        Recursively walk the path and yield matching blocks from the packed
        netlist
        """

        # No more path nodes to follow
        if not path:
            return

        # The block is "open"
        if block.is_open:
            return

        # Check if the current block is a LUT
        is_lut = len(block.blocks) == 1 and "lut[0]" in block.blocks and \
                 block.blocks["lut[0]"].is_leaf  # noqa: E127

        # Check if the block match the path node. Check type and mode
        block_node = PathNode.from_string(block.instance)
        block_node.mode = block.mode

        path_node = path[0]

        if block_node.name != path_node.name:
            return

        if not block.is_leaf:
            mode = "default" if is_lut else block_node.mode
            if path_node.mode != mode:
                return

        # If index is explicitly given check it as well
        if path_node.index is not None and path_node.index != block_node.index:
            return

        # This is a leaf block, add it
        if block.is_leaf and not block.is_open:
            assert len(path) == 1, (path, block)
            yield block

        # This is not a leaf block
        else:

            # Add the implicit LUT hierarchy to the path
            if is_lut:
                path.append(PathNode.from_string("lut[0]"))

            # Recurse
            for child in block.blocks.values():
                yield from walk(child, path[1:])
def load_clb_nets_into_pb_graph(clb_block, clb_graph):
    """
    Loads packed netlist of the given CLB block into its routing graph
    """

    # Annotate nodes with nets
    for node in clb_graph.nodes.values():

        # Disassemble node path parts
        parts = node.path.split(".")
        parts = [PathNode.from_string(p) for p in parts]

        # Check if the node belongs to this CLB
        block_inst = "{}[{}]".format(parts[0].name, parts[0].index)
        assert block_inst == clb_block.instance, (block_inst,
                                                  clb_block.instance)

        # Find the block referred to by the node
        block = get_block_by_path(clb_block, parts[1:-1])
        if block is None:
            continue

        # Find a corresponding port in the block
        node_port = parts[-1]
        if node_port.name not in block.ports:
            continue
        block_port = block.ports[node_port.name]

        # Find a net for the port pin and assign it
        net = block.find_net_for_port(block_port.name, node_port.index)
        node.net = net
Пример #4
0
    def __init__(self, net, block_type, port_spec):

        port = PathNode.from_string(port_spec)

        self.net = net
        self.block_type = block_type
        self.port = port.name
        self.pin = port.index
Пример #5
0
    def find(self, path):
        """
        Finds a pb_type or a mode given its hierarchical path.
        """

        # Split the path or assume this is an already splitted list.
        if isinstance(path, str):
            path = path.split(".")
            path = [PathNode.from_string(p) for p in path]
        else:
            assert isinstance(path, list), type(path)

        # Walk the hierarchy along the path
        pbtype = self
        while True:

            # Pop a node from the path
            part = path[0]
            path = path[1:]

            # The path node must not have an index
            assert part.index is None, part

            # Check name
            if part.name != pbtype.name:
                break

            # Explicit mode
            if part.mode is not None:
                if part.mode not in pbtype.modes:
                    break
                mode = pbtype.modes[part.mode]

                # No more path, return the mode
                if not path:
                    return mode

            # Mode not given
            else:

                # No more path, return the pb_type
                if not path:
                    return pbtype

                # Get the implicit mode
                if len(pbtype.modes) > 1:
                    break
                mode = next(iter(pbtype.modes.values()))

            # Find the child pb_type
            part = path[0]
            if part.name not in mode.pb_types:
                break

            pbtype = mode.pb_types[part.name]

        # Not found
        return None
Пример #6
0
def fix_block_path(block_path, arch_path, change_mode=True):
    """
    Given a hierarchical path without explicit modes and indices adds them to
    those nodes that match with the block path.

    The last node with matching name and index will have its mode changed to
    match the given path if change_mode is True.
    """

    # Get the full path (with indices and modes) to the block
    block_path = block_path.split(".")
    arch_path = arch_path.split(".")

    length = min(len(arch_path), len(block_path))

    # Process the path
    for i in range(length):

        # Parse both path nodes
        arch_node = PathNode.from_string(arch_path[i])
        block_node = PathNode.from_string(block_path[i])

        # Name doesn't match
        if arch_node.name != block_node.name:
            break
        # Index doesn't match
        if arch_node.index is not None and arch_node.index != block_node.index:
            break

        # Optionally change mode as in the architecture path
        if change_mode:
            mode = arch_node.mode if arch_node.mode else "default"
        else:
            mode = block_node.mode

        # Fix the node
        arch_path[i] = str(PathNode(block_node.name, block_node.index, mode))

        # If the mode does not match then break
        if arch_node.mode is not None and block_node.mode != "default":
            if arch_node.mode != block_node.mode:
                break

    # Join the modified path back
    return ".".join(arch_path)
Пример #7
0
    def walk(arch_path, pbtype, pbtype_index, curr_path=None):

        # Parse the path node
        if arch_path:
            path_node = PathNode.from_string(arch_path[0])
            arch_path = arch_path[1:]

        # No more path nodes, consider all wildcards
        else:
            path_node = PathNode(None, None, None)

        # Check if the name matches
        pbtype_name = pbtype.name
        if path_node.name is not None and path_node.name != pbtype_name:
            return

        # Check if the index matches
        if path_node.index is not None and path_node.index != pbtype_index:
            return

        # Initialize the current path if not given
        if curr_path is None:
            curr_path = []

        # This is a leaf pb_type. Yield path to it
        if pbtype.is_leaf:
            part = "{}[{}]".format(pbtype_name, pbtype_index)
            yield (".".join(curr_path + [part]), pbtype)

        # Recurse
        for mode_name, mode in pbtype.modes.items():

            # Check mode if given
            if path_node.mode is not None and path_node.mode != mode_name:
                continue

            # Recurse for children
            for child, i in mode.yield_children():

                # Recurse
                part = "{}[{}][{}]".format(pbtype_name, pbtype_index,
                                           mode_name)
                yield from walk(arch_path, child, i, curr_path + [part])
Пример #8
0
def expand_port_maps(rules, clb_pbtypes):
    """
    Expands port maps of repacking rules so that they explicitly specify
    port pins.
    """

    for rule in rules:

        # Get src and dst pb_types
        path = [PathNode.from_string(p) for p in rule.src.split(".")]
        path = [PathNode(p.name, mode=p.mode) for p in path]
        src_pbtype = clb_pbtypes[path[0].name].find(path)
        assert src_pbtype, ".".join([str(p) for p in path])

        path = [PathNode.from_string(p) for p in rule.dst.split(".")]
        path = [PathNode(p.name, mode=p.mode) for p in path]
        dst_pbtype = clb_pbtypes[path[0].name].find(path)
        assert dst_pbtype, ".".join([str(p) for p in path])

        # Expand port map
        port_map = {}
        for src_port, dst_port in rule.port_map.items():

            # Get pin lists
            src_pins = list(src_pbtype.yield_port_pins(src_port))
            dst_pins = list(dst_pbtype.yield_port_pins(dst_port))

            assert len(src_pins) == len(dst_pins), (src_pins, dst_pins)

            # Update port map
            for src_pin, dst_pin in zip(src_pins, dst_pins):
                port_map[src_pin] = dst_pin

        rule.port_map = port_map

    return rules
Пример #9
0
    def get_block_by_path(self, path):
        """
        Returns a child block given its hierarchical path. The path must not
        include the current block.

        The path may or may not contain modes. When a mode is given it will
        be used for matching. The path must contain indices.
        """

        def walk(block, parts):

            # Check if instance matches
            instance = "{}[{}]".format(parts[0].name, parts[0].index)
            if block.instance != instance:
                return None

            # Check if operating mode matches
            if parts[0].mode is not None:
                if block.mode != parts[0].mode:
                    return None

            # Next
            parts = parts[1:]

            # No more path parts, this is the block
            if not parts:
                return block

            # Find child block by its instance and recurse
            instance = "{}[{}]".format(parts[0].name, parts[0].index)
            if instance in block.blocks:
                return walk(block.blocks[instance], parts)

            return None

        # Prepend self to the path
        path = "{}[{}]".format(self.instance, self.mode) + "." + path

        # Split and parse the path
        path = path.split(".")
        path = [PathNode.from_string(p) for p in path]

        # Find the child
        return walk(self, path)
def build_packed_netlist_from_pb_graph(clb_graph):
    """
    This function builds a packed netlist fragment given an annotated (with
    nets) CLB graph. The created netlist fragment will be missing information:

      - Atom block names
      - Atom block attributes and parameters

    The missing information has to be supplied externally as it is not stored
    within a graph.

    The first step is to create the block hierarchy (without any ports yet).
    This is done by scanning the graph and creating blocks for nodes that
    belong to nets. Only those nodes are used here.

    The second stage is open block insertion. Whenever a non-leaf is in use
    it should have all of its children present. Unused ones should be makred
    as "open".

    The third stage is adding ports to the blocks and connectivity information.
    To gather all necessary information all the nodes of the graph are needed.

    The final step is leaf block name assignment. Leaf blocks get their names
    based on nets they drive. A special case is a block representing an output
    that doesn't drive anything. Such blocks get names based on their inputs
    but prefixed with "out:".
    """

    # Build node connectivity. These two maps holds upstream and downstream
    # node sets for a given node. They consider active nodes only.
    nodes_up = {}

    for edge in clb_graph.edges:

        # Check if the edge is active
        if clb_graph.edge_net(edge) is None:
            continue

        # Up map
        if edge.dst_id not in nodes_up:
            nodes_up[edge.dst_id] = set()
        nodes_up[edge.dst_id].add((edge.src_id, edge.ic))

    # Create the block hierarchy for nodes that have nets assigned.
    clb_block = None
    for node in clb_graph.nodes.values():

        # No net
        if node.net is None:
            continue

        # Disassemble node path parts
        parts = node.path.split(".")
        parts = [PathNode.from_string(p) for p in parts]

        # Create the root CLB
        instance = "{}[{}]".format(parts[0].name, parts[0].index)
        if clb_block is None:
            clb_block = packed_netlist.Block(
                name="clb",  # FIXME:
                instance=instance)
        else:
            assert clb_block.instance == instance

        # Follow the path, create blocks
        parent = clb_block
        for prev_part, curr_part in zip(parts[0:-2], parts[1:-1]):
            instance = "{}[{}]".format(curr_part.name, curr_part.index)
            parent_mode = prev_part.mode

            # Get an existing block
            if instance in parent.blocks:
                block = parent.blocks[instance]

            # Create a new block
            else:
                block = packed_netlist.Block(name="",
                                             instance=instance,
                                             parent=parent)
                block.name = "block@{:08X}".format(id(block))
                parent.blocks[instance] = block

            # Set / verify operating mode of the parent
            if parent_mode is not None:
                assert parent.mode in [None,
                                       parent_mode], (parent.mode, parent_mode)
                parent.mode = parent_mode

            # Next level
            parent = block

    # Check if the CLB got created
    assert clb_block is not None

    # Add open blocks.
    for node in clb_graph.nodes.values():

        # Consider only nodes without nets
        if node.net:
            continue

        # Disassemble node path parts
        parts = node.path.split(".")
        parts = [PathNode.from_string(p) for p in parts]

        if len(parts) < 3:
            continue

        # Find parent block
        parent = get_block_by_path(clb_block, parts[1:-2])
        if not parent:
            continue

        # Operating mode of the parent must match
        if parent.mode != parts[-3].mode:
            continue

        # Check if the parent contains an open block correesponding to this
        # node.
        curr_part = parts[-2]
        instance = "{}[{}]".format(curr_part.name, curr_part.index)

        if instance in parent.blocks:
            continue

        # Create an open block
        block = packed_netlist.Block(name="open",
                                     instance=instance,
                                     parent=parent)
        parent.blocks[block.instance] = block

    # Add block ports and their connections
    for node in clb_graph.nodes.values():

        # Disassemble node path parts
        parts = node.path.split(".")
        parts = [PathNode.from_string(p) for p in parts]

        # Find the block. If not found it meas that it is not active and
        # shouldn't be present in the netlist
        block = get_block_by_path(clb_block, parts[1:-1])
        if block is None:
            continue

        # The block is open, skip
        if block.is_open:
            continue

        # Get the port, add it if not present
        port_name = parts[-1].name
        port_type = node.port_type.name.lower()
        if port_name not in block.ports:

            # The relevant information will be updated as more nodes gets
            # discovered.
            port = packed_netlist.Port(name=port_name, type=port_type)
            block.ports[port_name] = port

        else:

            port = block.ports[port_name]
            assert port.type == port_type, (port.type, port_type)

        # Extend the port width if necessary
        bit_index = parts[-1].index
        port.width = max(port.width, bit_index + 1)

        # The port is not active, nothing more to be done here
        if node.net is None:
            continue

        # Identify driver of the port
        driver_node = None
        driver_conn = None
        if node.id in nodes_up:
            assert len(nodes_up[node.id]) <= 1, node.path
            driver_node, driver_conn = next(iter(nodes_up[node.id]))

        # Got a driver, this is an intermediate port
        if driver_node is not None:

            # Get the driver pb_type and port
            driver_path = clb_graph.nodes[driver_node].path.split(".")
            driver_path = [
                PathNode.from_string(driver_path[i]) for i in [-2, -1]
            ]

            # When a connection refers to the immediate parent do not include
            # block index suffix
            driver_instance = driver_path[0].name
            if block.parent is None or block.parent.type != driver_path[0].name:
                driver_instance += "[{}]".format(driver_path[0].index)

            # Add the connection
            port.connections[bit_index] = packed_netlist.Connection(
                driver_instance, driver_path[1].name, driver_path[1].index,
                driver_conn)

        # No driver, this is a source pin
        else:
            port.connections[bit_index] = node.net

    # Assign names to leaf blocks
    def leaf_walk(block):

        # A leaf
        if block.is_leaf and not block.is_open:

            # Identify all output pins that drive nets
            nets = []
            for port in block.ports.values():
                if port.type == "output":
                    for net in port.connections.values():
                        if isinstance(net, str):
                            nets.append(net)

            # No nets driven, this is an output pad
            if not nets:
                assert "outpad" in block.ports

                port = block.ports["outpad"]
                assert port.type == "input", port
                assert port.width == 1, port

                net = block.find_net_for_port("outpad", 0)
                nets = ["out:" + net]

            # Build block name and assign it
            if nets:
                block.name = "_".join(nets)

        # Recurse
        for child in block.blocks.values():
            leaf_walk(child)

    leaf_walk(clb_block)

    # Return the top-level CLB block
    return clb_block
Пример #11
0
def main():

    # Parse arguments
    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter)

    parser.add_argument("--vpr-arch",
                        type=str,
                        required=True,
                        help="VPR architecture XML file")
    parser.add_argument("--repacking-rules",
                        type=str,
                        required=True,
                        help="JSON file describing repacking rules")
    parser.add_argument("--json-constraints",
                        type=str,
                        default=None,
                        help="JSON file describing repacking constraints")
    parser.add_argument("--pcf-constraints",
                        type=str,
                        default=None,
                        help="PCF file describing repacking constraints")
    parser.add_argument("--eblif-in",
                        type=str,
                        required=True,
                        help="Input circuit netlist in BLIF/EBLIF format")
    parser.add_argument("--net-in",
                        type=str,
                        required=True,
                        help="Input VPR packed netlist (.net)")
    parser.add_argument("--place-in",
                        type=str,
                        default=None,
                        help="Input VPR placement file (.place)")
    parser.add_argument("--eblif-out",
                        type=str,
                        default=None,
                        help="Output circuit netlist BLIF/EBLIF file")
    parser.add_argument("--net-out",
                        type=str,
                        default=None,
                        help="Output VPR packed netlist (.net) file")
    parser.add_argument("--place-out",
                        type=str,
                        default=None,
                        help="Output VPR placement (.place) file")
    parser.add_argument(
        "--absorb_buffer_luts",
        type=str,
        default="on",
        choices=["on", "off"],
        help="Controls whether buffer LUTs are to be absorbed downstream")
    parser.add_argument("--dump-dot",
                        action="store_true",
                        help="Dump graphviz .dot files for pb_type graphs")
    parser.add_argument(
        "--dump-netlist",
        action="store_true",
        help="Dump .eblif files at different stages of EBLIF netlist processing"
    )
    parser.add_argument("--log",
                        type=str,
                        default=None,
                        help="Log file name (def. stdout)")
    parser.add_argument(
        "--log-level",
        type=str,
        choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
        default="WARNING",
        help="Log level (def. \"WARNING\")")

    args = parser.parse_args()
    init_time = time.perf_counter()

    absorb_buffer_luts = args.absorb_buffer_luts == "on"

    # Setup logging
    logging.basicConfig(
        filename=args.log,
        filemode="w",
        format="%(message)s",
        level=getattr(logging, args.log_level.upper()),
    )

    if args.log is not None:
        logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))

    # Re-assemble and log the commandline
    cmdline = " ".join([shlex.quote(a) for a in sys.argv])
    logging.debug("command line: {}".format(cmdline))

    # Load the VPR architecture
    logging.info("Loading VPR architecture file...")
    xml_tree = ET.parse(args.vpr_arch, ET.XMLParser(remove_blank_text=True))

    # Get CLBs
    xml_clbs = xml_tree.getroot().find("complexblocklist").findall("pb_type")
    xml_clbs = {clb.attrib["name"]: clb for clb in xml_clbs}

    # Build pb_type hierarchy for each CLB
    logging.info("Building pb_type hierarchy...")
    clb_pbtypes = {
        name: PbType.from_etree(elem)
        for name, elem in xml_clbs.items()
    }

    # Build a list of models
    logging.info("Building primitive models...")
    models = {}
    for pb_type in clb_pbtypes.values():
        models.update(Model.collect_models(pb_type))

    # DEBUG
    keys = sorted(list(models.keys()))
    for key in keys:
        logging.debug(" " + str(models[key]))

    # Load the repacking rules
    logging.info("Loading repacking rules...")
    with open(args.repacking_rules, "r") as fp:
        json_root = json.load(fp)

    # Get repacking rules
    repacking_rules = load_repacking_rules(json_root)
    # Expand port maps in repacking rules
    expand_port_maps(repacking_rules, clb_pbtypes)

    # Load the repacking constraints if provided

    if args.json_constraints is not None:
        logging.info("Loading JSON constraints...")

        with open(args.json_constraints, "r") as fp:
            json_root = json.load(fp)
            repacking_constraints = load_json_constraints(json_root)

    else:
        repacking_constraints = []

    if args.pcf_constraints is not None:
        logging.info("Loading PCF constraints...")

        with open(args.pcf_constraints, "r") as fp:
            repacking_constraints.extend(load_pcf_constraints(fp))

    # Load the BLIF/EBLIF file
    logging.info("Loading BLIF/EBLIF circuit netlist...")
    eblif = Eblif.from_file(args.eblif_in)

    # Clean the netlist
    logging.info("Cleaning circuit netlist...")

    if absorb_buffer_luts:
        net_map = netlist_cleaning.absorb_buffer_luts(eblif)
    else:
        net_map = {}

    # Optional dump
    if args.dump_netlist:
        eblif.to_file("netlist.cleaned.eblif")

    # Convert top-level inputs to cells
    eblif.convert_ports_to_cells()

    # Optional dump
    if args.dump_netlist:
        eblif.to_file("netlist.io_cells.eblif")

    # Load the packed netlist XML
    logging.info("Loading VPR packed netlist...")
    net_xml = ET.parse(args.net_in, ET.XMLParser(remove_blank_text=True))
    packed_netlist = PackedNetlist.from_etree(net_xml.getroot())

    # Count blocks
    total_blocks = 0
    for clb_block in packed_netlist.blocks.values():
        total_blocks += clb_block.count_leafs()
    logging.debug(" {} leaf blocks".format(total_blocks))

    init_time = time.perf_counter() - init_time
    repack_time = time.perf_counter()

    # Check if the repacking constraints do not refer to any non-existent nets
    if repacking_constraints:
        logging.info("Validating constraints...")

        all_nets = set()
        for clb_block in packed_netlist.blocks.values():
            all_nets |= clb_block.get_nets()

        constrained_nets = set([c.net for c in repacking_constraints])
        invalid_nets = constrained_nets - all_nets

        if invalid_nets:
            logging.critical(
                " Error: constraints refer to nonexistent net(s): {}".format(
                    ", ".join(invalid_nets)))
            exit(-1)

    # Process netlist CLBs
    logging.info("Processing CLBs...")

    leaf_block_names = {}

    route_through_net_ids = {}

    repacked_clb_count = 0
    repacked_block_count = 0

    for clb_block in packed_netlist.blocks.values():
        logging.debug(" " + str(clb_block))

        # Remap block and net names
        clb_block.rename_nets(net_map)

        # Find a corresponding root pb_type (complex block) in the architecture
        clb_pbtype = clb_pbtypes.get(clb_block.type, None)
        if clb_pbtype is None:
            logging.error(
                "Complex block type '{}' not found in the VPR arch".format(
                    clb_block.type))
            exit(-1)

        # Identify and fixup route-throu LUTs
        logging.debug("  Identifying route-throu LUTs...")
        net_pairs = fixup_route_throu_luts(clb_block, route_through_net_ids)
        insert_buffers(net_pairs, eblif, clb_block)

        # Identify blocks to repack. Continue to next CLB if there are none
        logging.debug("  Identifying blocks to repack...")
        blocks_to_repack = identify_blocks_to_repack(clb_block,
                                                     repacking_rules)
        if not blocks_to_repack:
            continue

        # For each block to be repacked identify its destination candidate(s)
        logging.debug("  Identifying repack targets...")
        iter_list = list(blocks_to_repack)
        blocks_to_repack = []
        for block, rule in iter_list:

            # Remap index of the destination block pointed by the path of the
            # rule.
            blk_path = block.get_path()
            blk_path = [PathNode.from_string(p) for p in blk_path.split(".")]
            dst_path = rule.dst
            dst_path = [PathNode.from_string(p) for p in dst_path.split(".")]

            if dst_path[-1].index is None:
                dst_path[-1].index = rule.remap_pb_type_index(
                    blk_path[-1].index)

            blk_path = ".".join([str(p) for p in blk_path])
            dst_path = ".".join([str(p) for p in dst_path])

            # Fix the part of the destination block path so that it matches the
            # path of the block to be remapped
            arch_path = fix_block_path(blk_path, dst_path)

            # Identify target candidates
            candidates = identify_repack_target_candidates(
                clb_pbtype, arch_path)
            assert candidates, (block, arch_path)

            logging.debug("   {} ({})".format(str(block), rule.src))
            for path, pbtype_xml in candidates:
                logging.debug("    " + str(path))

            # No candidates
            if not candidates:
                logging.critical("No repack target found!")
                exit(-1)

            # There must be only a single repack target per block
            if len(candidates) > 1:
                logging.critical(
                    "Multiple repack targets found! {}".format(candidates))
                exit(-1)

            # Store concrete correspondence
            # (packed netlist block, repacking rule, (target path, target pb_type))
            blocks_to_repack.append((block, rule, candidates[0]))

        if not blocks_to_repack:
            continue

        # Check for conflicts
        repack_targets = set()
        for block, rule, (path, pbtype) in blocks_to_repack:

            if path in repack_targets:
                logging.error(
                    "Multiple blocks are to be repacked into '{}'".format(
                        path))
            repack_targets.add(path)

        # Stats
        repacked_clb_count += 1
        repacked_block_count += len(blocks_to_repack)

        # Repack the circuit netlist
        logging.debug("  Repacking circuit netlist...")
        for src_block, rule, (dst_path, dst_pbtype) in blocks_to_repack:
            logging.debug("   " + str(src_block))

            # Find the original pb_type
            src_path = src_block.get_path(with_indices=False)
            src_pbtype = clb_pbtype.find(src_path)
            assert src_pbtype is not None, src_path

            # Get the destination BLIF model
            assert dst_pbtype.blif_model is not None, dst_pbtype.name
            dst_blif_model = dst_pbtype.blif_model.split(maxsplit=1)[-1]

            if dst_blif_model in [".input", ".output"]:
                continue

            # Get the model object
            assert dst_blif_model in models, dst_blif_model
            model = models[dst_blif_model]

            # Find the cell in the netlist
            assert src_block.name in eblif.cells, src_block.name
            cell = eblif.cells[src_block.name]

            # Store the leaf block name so that it can be restored after
            # repacking
            leaf_block_names[dst_path] = cell.name

            # Repack it
            repack_netlist_cell(
                eblif,
                cell,
                src_block,
                src_pbtype,
                model,
                rule,
            )

        # Build a pb routing graph for this CLB
        logging.debug("  Building pb_type routing graph...")
        clb_xml = xml_clbs[clb_block.type]
        graph = Graph.from_etree(clb_xml, clb_block.instance)

        # Dump original packed netlist graph as graphvis .dot file
        if args.dump_dot:
            load_clb_nets_into_pb_graph(clb_block, graph)
            fname = "graph_original_{}.dot".format(clb_block.instance)
            with open(fname, "w") as fp:
                fp.write(graph.dump_dot(color_by="net", nets_only=True))
            graph.clear_nets()

        # Annotate source and sinks with nets
        logging.debug("  Annotating net endpoints...")

        # For the CLB
        logging.debug("   " + str(clb_block))
        annotate_net_endpoints(clb_graph=graph,
                               block=clb_block,
                               constraints=repacking_constraints)

        # For repacked leafs
        for block, rule, (path, dst_pbtype) in blocks_to_repack:
            logging.debug("   " + str(block))

            # Get the destination BLIF model
            assert dst_pbtype.blif_model is not None, dst_pbtype.name
            dst_blif_model = dst_pbtype.blif_model.split(maxsplit=1)[-1]

            # Annotate
            annotate_net_endpoints(clb_graph=graph,
                                   block=block,
                                   block_path=path,
                                   port_map=rule.port_map)

        # Initialize router
        logging.debug("  Initializing router...")
        router = Router(graph)

        # There has to be at least one net in the block after repacking
        assert router.nets, "No nets"

        # Route
        logging.debug("  Routing...")
        router.route_nets(debug=True)

        # Build packed netlist CLB from the graph
        logging.debug("  Rebuilding CLB netlist...")
        repacked_clb_block = build_packed_netlist_from_pb_graph(graph)
        repacked_clb_block.rename_cluster(clb_block.name)

        # Restore names of leaf blocks
        for src_block, rule, (dst_path, dst_pbtype) in blocks_to_repack:
            if dst_path in leaf_block_names:

                search_path = dst_path.split(".", maxsplit=1)[1]
                dst_block = repacked_clb_block.get_block_by_path(search_path)
                assert dst_block is not None, dst_path

                name = leaf_block_names[dst_path]
                logging.debug("   renaming leaf block {} to {}".format(
                    dst_block, name))
                dst_block.name = name

        # Replace the CLB
        packed_netlist.blocks[clb_block.instance] = repacked_clb_block

        # Dump repacked packed netlist graph as graphviz .dot file
        if args.dump_dot:
            fname = "graph_repacked_{}.dot".format(clb_block.instance)
            with open(fname, "w") as fp:
                fp.write(graph.dump_dot(color_by="net", nets_only=True))

    # Optional dump
    if args.dump_netlist:
        eblif.to_file("netlist.repacked.eblif")
        write_packed_netlist("netlist.repacked.net", packed_netlist)

    # Synchronize packed netlist attributes and parameters with EBLIF
    syncrhonize_attributes_and_parameters(eblif, packed_netlist)

    repack_time = time.perf_counter() - repack_time
    writeout_time = time.perf_counter()

    # FIXME: The below code absorbs buffer LUTs because it couldn't be done
    # in the beginning to preserve output names. However the code has evolved
    # and now should correctly handle absorption of output nets into input
    # nets not only the opposite as it did before. So theoretically the buffer
    # absorption below may be removed and the invocation at the beginning of
    # the flow changed to use outputs=True.

    # Convert cells into top-level ports
    eblif.convert_cells_to_ports()

    # Clean the circuit netlist again. Need to do it here again as LUT buffers
    # driving top-level inputs couldn't been swept before repacking as it
    # would cause top-level port renaming.
    logging.info("Cleaning repacked circuit netlist...")
    if absorb_buffer_luts:

        net_map = netlist_cleaning.absorb_buffer_luts(eblif, outputs=True)

        # Synchronize packed netlist net names
        for block in packed_netlist.blocks.values():
            block.rename_nets(net_map)

    # Optional dump
    if args.dump_netlist:
        eblif.to_file("netlist.repacked_and_cleaned.eblif")

    # Write the circuit netlist
    logging.info("Writing EBLIF circuit netlist...")
    fname = args.eblif_out if args.eblif_out else "repacked.eblif"
    eblif.to_file(fname, consts=False)

    # Compute SHA256 digest of the EBLIF file and store it in the packed
    # netlist.
    with open(fname, "rb") as fp:
        digest = hashlib.sha256(fp.read()).hexdigest()
    packed_netlist.netlist_id = "SHA256:" + digest

    # Write the packed netlist
    logging.info("Writing VPR packed netlist...")
    net_out_fname = args.net_out if args.net_out else "repacked.net"
    write_packed_netlist(net_out_fname, packed_netlist)

    writeout_time = time.perf_counter() - writeout_time

    # Read and patch SHA and packed netlist name in the VPR placement file
    # if given
    if args.place_in:
        logging.info("Patching VPR placement file...")

        # Compute .net file digest
        with open(net_out_fname, "rb") as fp:
            net_digest = hashlib.sha256(fp.read()).hexdigest()

        # Read placement
        with open(args.place_in, "r") as fp:
            placement = fp.readlines()

        # Find the header line
        for i in range(len(placement)):
            if placement[i].startswith("Netlist_File:"):

                # Replace the header
                placement[i] = "Netlist_File: {} Netlist_ID: {}\n".format(
                    os.path.basename(net_out_fname), "SHA256:" + net_digest)
                break
        else:
            logging.warn(" The placement file '{}' has no header!".format(
                args.place_in))

        # Write the patched placement
        fname = args.place_out if args.place_out else "repacked.place"
        with open(fname, "w") as fp:
            fp.writelines(placement)

    # Count blocks
    total_blocks = 0
    for clb_block in packed_netlist.blocks.values():
        total_blocks += clb_block.count_leafs()

    # Print statistics
    logging.info("Finished.")

    logging.info("")
    logging.info("Initialization time: {:.2f}s".format(init_time))
    logging.info("Repacking time     : {:.2f}s".format(repack_time))
    logging.info("Finishing time     : {:.2f}s".format(writeout_time))
    logging.info("Repacked CLBs      : {}".format(repacked_clb_count))
    logging.info("Repacked blocks    : {}".format(repacked_block_count))
    logging.info("Total blocks       : {}".format(total_blocks))
Пример #12
0
def repack_netlist_cell(eblif,
                        cell,
                        block,
                        src_pbtype,
                        model,
                        rule,
                        def_map=None):
    """
    This function transforms circuit netlist (BLIF / EBLIF) cells to implement
    re-packing.
    """

    # Build a mini-port map for ports of build-in cells (.names, .latch)
    # this is needed to correlate pb_type ports with model ports.
    class_map = {}
    for port in src_pbtype.ports.values():
        if port.cls is not None:
            class_map[port.cls] = port.name

    # Get LUT in port if the cell is a LUT
    lut_in = class_map.get("lut_in", None)

    # Create a new cell
    repacked_cell = Cell(model.name)
    repacked_cell.name = cell.name

    # Copy cell data
    repacked_cell.cname = cell.cname
    repacked_cell.attributes = cell.attributes
    repacked_cell.parameters = cell.parameters

    # Remap port connections
    lut_rotation = {}
    lut_width = 0

    for port, net in cell.ports.items():
        port = PathNode.from_string(port)

        # If the port name refers to a port class then remap it
        port.name = class_map.get(port.name, port.name)

        # Add port index for 1-bit ports
        if port.index is None:
            port.index = 0

        org_index = port.index

        # Undo VPR port rotation
        blk_port = block.ports[port.name]
        if blk_port.rotation_map:
            inv_rotation_map = {v: k for k, v in blk_port.rotation_map.items()}
            port.index = inv_rotation_map[port.index]

        # Remap the port
        if rule.port_map is not None:
            key = (port.name, port.index)
            if key in rule.port_map:
                name, index = rule.port_map[key]
                port = PathNode(name, index)

        # Remove port index for 1-bit ports
        width = model.ports[port.name].width
        if width == 1:
            port.index = None

        repacked_cell.ports[str(port)] = net

        # Update LUT rotation if applicable
        if port.name == lut_in:
            assert port.index not in lut_rotation
            lut_rotation[port.index] = org_index
            lut_width = width

    # If the cell is a LUT then rotate its truth table. Append the rotated
    # truth table as a parameter to the repacked cell.
    if cell.type == "$lut":

        # Build the init parameter
        init = rotate_truth_table(cell.init, lut_rotation)
        init = "".join(["1" if x else "0" for x in init][::-1])

        # Expand the truth table to match the physical LUT width. Do that by
        # repeating the lower part of it until the desired length is attained.
        while (len(init).bit_length() - 1) < lut_width:
            init = init + init

        # Reverse LUT bit order
        init = init[::-1]

        repacked_cell.parameters["LUT"] = init

    # If the cell is a LUT-based const generator append the LUT parameter as
    # well.
    if cell.type == "$const":

        assert lut_width == 0, (cell, lut_width)

        # Assume that the model is a LUT. Take its widest input port and use
        # its width as LUT size.
        max_width = -1
        for port in model.ports.values():
            if port.type == PortType.INPUT:
                if port.width > max_width:
                    max_width = port.width

        init = str(cell.init) * (1 << max_width)
        repacked_cell.parameters["LUT"] = init

    # Process parameters for "adder_lut4"
    if cell.type == "adder_lut4":

        # Remap the Cin mux select to MODE
        if "IN2_IS_CIN" in cell.parameters:
            repacked_cell.parameters["MODE"] = cell.parameters["IN2_IS_CIN"]
            del repacked_cell.parameters["IN2_IS_CIN"]

        # Reverse LUT bit order
        repacked_cell.parameters["LUT"] = repacked_cell.parameters["LUT"][::-1]

    # If the rule contains mode bits then append the MODE parameter to the cell
    if rule.mode_bits:
        repacked_cell.parameters["MODE"] = rule.mode_bits

    # Check for unconnected ports that should be tied to some default nets
    if def_map:
        for key, net in def_map.items():
            port = "{}[{}]".format(*key)
            if port not in repacked_cell.ports:
                repacked_cell.ports[port] = net

    # Remove the old cell and replace it with the new one
    del eblif.cells[cell.name]
    eblif.cells[repacked_cell.name] = repacked_cell

    return repacked_cell
Пример #13
0
def annotate_net_endpoints(clb_graph,
                           block,
                           block_path=None,
                           constraints=None,
                           port_map=None,
                           def_map=None):
    """
    This function annotates SOURCE and SINK nodes of the block pointed by
    block_path with nets of their corresponding ports but from the other given
    block.

    This essentially does the block re-packing at the packed netlist level.
    """

    # Invert the port map (is src->dst, we need dst->src)
    if port_map is not None:
        inv_port_map = {v: k for k, v in port_map.items()}

    # Get block path
    if block_path is None:
        block_path = block.get_path()

    # Remove mode from the last node of the path
    # Get the destination block type
    block_path = [PathNode.from_string(p) for p in block_path.split(".")]
    block_type = block_path[0].name
    block_path[-1].mode = None
    block_path = ".".join([str(p) for p in block_path])

    # Identify and annotate SOURCE and SINK nodes
    source_and_sink_nodes = []
    nodes_by_net = {}

    for node in clb_graph.nodes.values():

        # Consider only SOURCE and SINK nodes
        if node.type not in [NodeType.SOURCE, NodeType.SINK]:
            continue

        # Split the node path into block and port
        path, port = node.path.rsplit(".", maxsplit=1)

        # Check if the node belongs to this CLB
        if path != block_path:
            continue

        source_and_sink_nodes.append(node)
        port = PathNode.from_string(port)

        # Optionally remap the port
        if port_map is not None:
            key = (port.name, port.index)
            if key in inv_port_map:
                name, index = inv_port_map[key]
                port = PathNode(name, index)

        # Got this port in the source block
        # Find a net for the port pin of the source block and assign it
        net = None
        if port.name in block.ports:
            net = block.find_net_for_port(port.name, port.index)

        # If the port is unconnected then check if there is a defautil value
        # in the map
        if def_map:
            key = (port.name, port.index)
            if not net and key in def_map:
                net = def_map[key]
                logging.debug(
                    "    Unconnected port '{}' defaults to {}".format(
                        port, net))

        # Skip unconnected ports
        if not net:
            logging.debug("    Port '{}' is unconnected".format(port))
            continue

        # Assign the net
        node.net = net

        if net not in nodes_by_net:
            nodes_by_net[net] = []
        nodes_by_net[net].append(node)

    # No constraints, finish here
    if constraints is None:
        return

    # Reassign top-level SOURCE and SINK nodes according to the constraints
    for constraint in constraints:

        # Check if the constraint is for this block type
        if constraint.block_type != block_type:
            continue

        # Check if the net is available
        if constraint.net not in nodes_by_net:
            continue

        # Find a node for the destination port of the constraint. Throw an
        # error if not found
        for node in source_and_sink_nodes:
            _, port = node.path.rsplit(".", maxsplit=1)
            port = PathNode.from_string(port)

            if (port.name, port.index) == (constraint.port, constraint.pin):
                port_node = node
                break

        else:
            logging.critical("Cannot find port '{}' of block type '{}'".format(
                PathNode(constraint.port, constraint.pin).to_string(),
                block_type))
            exit(-1)

        # Check if we are not trying to constraint an input net to an output
        # port or vice-versa.
        node_types = set([node.type for node in nodes_by_net[constraint.net]])
        if port_node.type not in node_types:

            name_map = {NodeType.SINK: "output", NodeType.SOURCE: "input"}

            logging.warning(
                "Cannot constrain {} net '{}' to {} port '{}'".format(
                    name_map[next(iter(node_types))],
                    constraint.net,
                    name_map[port_node.type],
                    PathNode(constraint.port, constraint.pin).to_string(),
                ))
            continue

        # Remove the net from any node of the same type as the destination one
        for node in nodes_by_net[constraint.net]:
            if node.type == port_node.type:
                node.net = None

        # Assign the net to the port
        port_node.net = constraint.net
        logging.debug("    Constraining net '{}' to port '{}'".format(
            constraint.net,
            PathNode(constraint.port, constraint.pin).to_string()))
Пример #14
0
def identify_repack_target_candidates(clb_pbtype, path):
    """
    Given a hierarchical path and a root CLB identifies all leaf pb_types that
    match that path and yields them.

    The path may be "fixed" (having explicit modes and pb indices) up to some
    depth. When a path node refers to a concrete pb_type then the algorightm
    follows exactly that path.

    For non-fixed path nodes the algorithm explores all possiblities and yields
    them.
    """
    def walk(arch_path, pbtype, pbtype_index, curr_path=None):

        # Parse the path node
        if arch_path:
            path_node = PathNode.from_string(arch_path[0])
            arch_path = arch_path[1:]

        # No more path nodes, consider all wildcards
        else:
            path_node = PathNode(None, None, None)

        # Check if the name matches
        pbtype_name = pbtype.name
        if path_node.name is not None and path_node.name != pbtype_name:
            return

        # Check if the index matches
        if path_node.index is not None and path_node.index != pbtype_index:
            return

        # Initialize the current path if not given
        if curr_path is None:
            curr_path = []

        # This is a leaf pb_type. Yield path to it
        if pbtype.is_leaf:
            part = "{}[{}]".format(pbtype_name, pbtype_index)
            yield (".".join(curr_path + [part]), pbtype)

        # Recurse
        for mode_name, mode in pbtype.modes.items():

            # Check mode if given
            if path_node.mode is not None and path_node.mode != mode_name:
                continue

            # Recurse for children
            for child, i in mode.yield_children():

                # Recurse
                part = "{}[{}][{}]".format(pbtype_name, pbtype_index,
                                           mode_name)
                yield from walk(arch_path, child, i, curr_path + [part])

    # Split the path
    path = path.split(".")

    # Get CLB index from the path
    part = PathNode.from_string(path[0])
    clb_index = part.index

    # Begin walk
    candidates = list(walk(path, clb_pbtype, clb_index))
    return candidates
Пример #15
0
def identify_blocks_to_repack(clb_block, repacking_rules):
    """
    Identifies all blocks in the packed netlist that require re-packing
    """
    def walk(block, path):
        """
        Recursively walk the path and yield matching blocks from the packed
        netlist
        """

        # No more path nodes to follow
        if not path:
            return

        # The block is "open"
        if block.is_open:
            return

        # Check if the current block is a LUT
        is_lut = len(block.blocks) == 1 and "lut[0]" in block.blocks and \
                 block.blocks["lut[0]"].is_leaf  # noqa: E127

        # Check if the block match the path node. Check type and mode
        block_node = PathNode.from_string(block.instance)
        block_node.mode = block.mode

        path_node = path[0]

        if block_node.name != path_node.name:
            return

        if not block.is_leaf:
            mode = "default" if is_lut else block_node.mode
            if path_node.mode != mode:
                return

        # If index is explicitly given check it as well
        if path_node.index is not None and path_node.index != block_node.index:
            return

        # This is a leaf block, add it
        if block.is_leaf and not block.is_open:
            assert len(path) == 1, (path, block)
            yield block

        # This is not a leaf block
        else:

            # Add the implicit LUT hierarchy to the path
            if is_lut:
                path.append(PathNode.from_string("lut[0]"))

            # Recurse
            for child in block.blocks.values():
                yield from walk(child, path[1:])

    # For each rule
    blocks_to_repack = []
    for rule in repacking_rules:
        logging.debug("   checking rule path '{}'...".format(rule.src))

        # Parse the path
        path = [PathNode.from_string(p) for p in rule.src.split(".")]

        # Substitute non-explicit modes with "default" to match the packed
        # netlist
        for part in path:
            if part.mode is None:
                part.mode = "default"

        # Walk
        for block in walk(clb_block, path):
            logging.debug("    " + str(block))

            # Append to list
            blocks_to_repack.append((block, rule))

    return blocks_to_repack