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
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
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
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
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)
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])
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
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
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))
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
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()))
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
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