255 lines
8.5 KiB
Python
Executable File
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]
|