OpenFPGA/openfpga_flow/scripts/vpr_io_place.py

255 lines
8.5 KiB
Python
Executable File

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]