from __future__ import print_function from collections import OrderedDict, namedtuple import itertools import re import eblif import lxml.etree as ET IoConstraint = namedtuple('IoConstraint', 'name x y z comment') HEADER_TEMPLATE = """\ #{name:<{nl}} x y z pcf_line #{s:-^{nl}} -- -- - ----""" CONSTRAINT_TEMPLATE = '{name:<{nl}} {x: 3} {y: 3} {z: 2} # {comment}' INOUT_REGEX = re.compile(r"^(.+)(_\$inp|_\$out)(.*)$") NETNAME_REGEX = re.compile(r"(.+?)(\[[0-9]+\]$|$)") class IoPlace(object): def __init__(self): self.constraints = OrderedDict() self.inputs = set() self.outputs = set() self.net_to_block = None self.net_map = {} self.block_to_inst = {} self.inout_nets = set() self.net_to_pad = set() self.net_file_io = set() def read_io_loc_pairs(self, blif): """ Read IO_LOC_PAIRS parameters from eblif carrying the information which package pin a specified top port is constrained, e.g. O_LOC_PAIRS = "portA:D1" In case of differential inputs/outputs there are two pairs of the parameter, i.e. IO_LOC_PAIRS = "portA_p:D2,portA_n:D4" """ if 'subckt' not in blif: return for attr in blif['subckt']: if 'param' not in attr: continue if 'IO_LOC_PAIRS' in attr['param']: locs = attr['param']['IO_LOC_PAIRS'][1:-1].split(',') if 'NONE' in locs: continue for loc in locs: net, pad = loc.split(':') self.net_to_pad.add((net, pad)) def read_io_list_from_eblif(self, eblif_file): blif = eblif.parse_blif(eblif_file) self.inputs = set(blif['inputs']['args']) self.outputs = set(blif['outputs']['args']) # Build a net name map that maps products of an inout port split into # their formet name. # # For example, an inout port 'A' is split into 'A_$inp' and 'A_$out', # port B[2] into 'B_$inp[2]' and 'B_$out[2]'. self.net_map = {} self.inout_nets = set() for net in itertools.chain(self.inputs, self.outputs): match = INOUT_REGEX.match(net) if match: alias = match.group(1) + match.group(3) self.inout_nets.add(alias) self.net_map[net] = alias else: self.net_map[net] = net self.read_io_loc_pairs(blif) def load_block_names_from_net_file(self, net_file): """ .place files expect top-level block (cluster) names, not net names, so build a mapping from net names to block names from the .net file. """ net_xml = ET.parse(net_file) net_root = net_xml.getroot() self.net_to_block = {} for block in net_root.xpath("//block"): instance = block.attrib["instance"] if instance != "inpad[0]" and instance != "outpad[0]": continue top_block = block.getparent() assert top_block is not None while top_block.getparent() is not net_root: assert top_block is not None top_block = top_block.getparent() self.net_to_block[block.get("name")] = top_block.get("name") # Loop over all top-level blocks. Store block name to its instance # correspondences. for block_xml in net_root.findall("block"): name = block_xml.attrib["name"] inst = block_xml.attrib["instance"] assert name not in self.block_to_inst, block_xml.attrib self.block_to_inst[name] = inst def load_net_file_ios(self, net_file): """ Loads input and outputs net names from the netlist file. """ def get_ios(net_root, io_types): "Get the top level io signals from the netlist XML root." for io_type in io_types: io = net_root.xpath("/block/{}/text ()".format(io_type)) if len(io) == 1: yield io[0] net_xml = ET.parse(net_file) net_root = net_xml.getroot() for io_line in get_ios(net_root, ["inputs", "outputs"]): io_list = io_line.split(" ") for io in io_list: self.net_file_io.add(io.replace("out:", "")) def get_top_level_block_instance_for_net(self, net_name): """ Returns a name of the top-level block instance for the given net name. """ assert self.is_net(net_name) # VPR prefixes output constraints with "out:" if net_name in self.outputs: net_name = 'out:' + net_name # This is an inout net if net_name in self.inout_nets: block_names = set() for prefix, suffix in zip(["", "out:"], ["_$inp", "_$out"]): match = NETNAME_REGEX.match(net_name) name = prefix + match.group(1) + suffix + match.group(2) block_names.add(self.net_to_block[name]) # Both parts of the net should point to the same block assert len(block_names) == 1, (net_name, block_names) return self.block_to_inst[list(block_names)[0]] # A regular net else: if net_name in self.net_to_block: block_name = self.net_to_block[net_name] return self.block_to_inst[block_name] else: return None def constrain_net(self, net_name, loc, comment=""): assert len(loc) == 3 assert net_name not in self.constraints assert self.is_net(net_name), "net {} not in eblif".format(net_name) # VPR prefixes output constraints with "out:" if net_name in self.outputs: net_name = 'out:' + net_name # This is an inout net if net_name in self.inout_nets: for prefix, suffix in zip(["", "out:"], ["_$inp", "_$out"]): match = NETNAME_REGEX.match(net_name) name = prefix + match.group(1) + suffix + match.group(2) self.constraints[name] = IoConstraint( name=name, x=loc[0], y=loc[1], z=loc[2], comment=comment, ) # A regular net else: self.constraints[net_name] = IoConstraint( name=net_name, x=loc[0], y=loc[1], z=loc[2], comment=comment, ) def output_io_place(self, f): max_name_length = max(len(c.name) for c in self.constraints.values()) print( HEADER_TEMPLATE.format( name="Block Name", nl=max_name_length, s="" ), file=f ) constrained_blocks = {} for vpr_net, constraint in self.constraints.items(): name = constraint.name name = self.net_to_block.get(name) if self.net_to_block else name # This block is already constrained, check if there is no # conflict there. if name in constrained_blocks: existing = constrained_blocks[name] if existing.x != constraint.x or\ existing.y != constraint.y or\ existing.z != constraint.z: print( "Error: block '{}' has multiple conflicting constraints!" .format(name) ) print("", constrained_blocks[name]) print("", constraint) exit(-1) # Don't write the second constraing continue # omit if no corresponding block name for the net if name is not None: print( CONSTRAINT_TEMPLATE.format( name=name, nl=max_name_length, x=constraint.x, y=constraint.y, z=constraint.z, comment=constraint.comment ), file=f ) # Add to constrained block list constrained_blocks[name] = constraint def is_net(self, net): return net in self.net_map.values() def is_net_packed(self, net): return net in self.net_file_io def get_nets(self): for net in self.inputs: yield self.net_map[net] for net in self.outputs: yield self.net_map[net]