blob: 28af7738e961749b1982b19c3913504b10207353 [file] [log] [blame]
#!/usr/bin/env python
import argparse
import sys
import os
import re
from collections import OrderedDict
from lxml import etree as ET
class AuxInfo(object):
nodes_filepath = None
nets_filepath = None
weghts_filepath = None
placement_filepath = None
scl_filepath = None
lib_filepath = None
class Cell(object):
def __init__(self):
self.name = None
self.pins = []
class CellPin(object):
def __init__(self):
self.name = None
self.dir = None
self.is_clock = False
self.is_ctrl = False
self.num_pins = 1
class Net(object):
#Use fixed attributes to save runtime/memory
__slots__ = ('name', 'pins')
def __init__(self):
self.name = None
self.pins = []
class NetPin(object):
#Use fixed attributes to save runtime/memory
__slots__ = ('inst_name', 'pin_name')
def __init__(self):
self.inst_name = None
self.pin_name = None
class NodePin(object):
#Use fixed attributes to save runtime/memory
__slots__ = ('pin_name', 'net_name')
def __init__(self):
self.pin_name = None
self.net_name = None
class DeviceGrid(object):
def __init__(self):
self.width = None
self.height = None
self.sites = []
class DeviceResource(object):
def __init__(self):
self.name = None
self.primitives = []
class DeviceSite(object):
def __init__(self):
self.name = None
self.resources = []
def is_input(cell_type):
if cell_type == 'IBUF':
return True
return False
def is_output(cell_type):
if cell_type == 'OBUF':
return True
return False
def parse_args():
parser = argparse.ArgumentParser("Script to convert ISPD FPGA Bookshelf format benchmarks into VTR compatible formats")
parser.add_argument("aux_file",
help='.aux file describing the benchmark')
parser.add_argument("--benchmark",
default=None,
help='File name for output .blif file')
parser.add_argument("--arch",
default=None,
help='Filename for a partial VTR architecture')
parser.add_argument("--constraints",
default=None,
help='Filename for writing constraints')
parser.add_argument("--merge_arch_ports",
default=True,
help='Merge multi-bit ports in architecture. Default: %(default)s')
return parser.parse_args()
def main():
args = parse_args()
aux_info = parse_aux(args.aux_file)
if args.benchmark:
bookshelf2blif(aux_info, args.benchmark, merge_ports=args.merge_arch_ports)
if args.arch:
bookshelf2arch(aux_info, args.arch, merge_ports=args.merge_arch_ports)
if not args.arch and not args.benchmark:
assert False, 'Must specify one of --arch or --benchmark'
def parse_aux(aux_filepath):
dirname = os.path.dirname(aux_filepath)
aux_info = AuxInfo()
with open(aux_filepath) as f:
for line in f:
if line.startswith('#'):
continue
if line.startswith('design'):
prefix, info = line.split(':')
for filename in info.split():
filepath = os.path.join(dirname, filename)
if filename.endswith('.nodes'):
aux_info.nodes_filepath = filepath
elif filename.endswith('.nets'):
aux_info.nets_filepath = filepath
elif filename.endswith('.wts'):
aux_info.weights_filepath = filepath
elif filename.endswith('.pl'):
aux_info.placement_filepath = filepath
elif filename.endswith('.scl'):
aux_info.scl_filepath = filepath
elif filename.endswith('.lib'):
aux_info.lib_filepath = filepath
else:
assert False, 'Unrecognized file type'
else:
assert False, 'Unrecognized line in .aux file'
return aux_info
def bookshelf2blif(aux_info, out_filepath, merge_ports=False):
cells = parse_lib(aux_info, merge_ports)
nodes = parse_nodes(aux_info)
nets = parse_nets(aux_info)
#Inferr PIs/POs which are implicit in bookshelf format
inputs = OrderedDict()
outputs = OrderedDict()
for node_name, type in nodes.iteritems():
net_name = None
pin_name = None
if is_input(type):
net_name = node_name + '_in'
inputs[node_name] = net_name
pin_name = 'I'
elif is_output(type):
net_name = node_name + '_out'
outputs[node_name] = net_name
pin_name = 'O'
else:
continue
assert net_name is not None
assert pin_name is not None
#Create the net
net = Net()
net.name = net_name
net_pin = NetPin()
net_pin.inst_name = node_name
net_pin.pin_name = pin_name
net.pins.append(net_pin)
nets.append(net)
#Node to net mapping
node_pins = OrderedDict()
for net in nets:
for net_pin in net.pins:
assert net_pin.inst_name is not None
assert net_pin.pin_name is not None
if net_pin.inst_name not in node_pins:
node_pins[net_pin.inst_name] = []
node_pin = NodePin()
node_pin.net_name = net.name
node_pin.pin_name = net_pin.pin_name
node_pins[net_pin.inst_name].append(node_pin)
with open(out_filepath, 'w') as f:
print >>f, '#Generated with {}'.format(' '.join(sys.argv))
print >>f, ''
print >>f, '.model top'
print >>f, '.inputs \\'
pretty_print_list(f, inputs.values())
print >>f, '.outputs \\'
pretty_print_list(f, outputs.values())
for node_name, type in nodes.iteritems():
print >>f, ''
print >>f, '#{}'.format(node_name)
print >>f, '.subckt {} \\'.format(type)
pins = sorted(node_pins[node_name], key=lambda x: x.pin_name)
for i, node_pin in enumerate(pins):
print >>f, ' {}={}'.format(node_pin.pin_name, node_pin.net_name),
if i != len(node_pins[node_name]) - 1:
print >>f, '\\'
else:
print >>f, ''
print >>f, '.end '
print >>f, ''
for cell in cells:
print >>f, ""
print >>f, ".model {}".format(cell.name)
input_names = [cell_pin.name for cell_pin in cell.pins if cell_pin.dir == 'INPUT']
output_names = [cell_pin.name for cell_pin in cell.pins if cell_pin.dir == 'OUTPUT']
print >>f, ".inputs \\"
pretty_print_list(f, input_names)
print >>f, ".outputs \\"
pretty_print_list(f, output_names)
print >>f, ".blackbox"
print >>f, ".end"
print >>f, '#EOF'
def bookshelf2arch(aux_info, out_filepath, merge_ports=False):
cells = parse_lib(aux_info, merge_ports=merge_ports)
grid, sites, resources = parse_scl(aux_info)
root = ET.Element('architecture')
for cell in cells:
add_arch_model(root, cell)
for site in sites:
add_arch_block(root, site, resources, cells)
add_arch_grid(root, grid)
with open(out_filepath, 'w') as f:
f.write(ET.tostring(root, pretty_print=True))
def parse_lib(aux_info, merge_ports=False):
index_regex = re.compile(r'(?P<name>.*)\[(?P<index>\d+)\]')
cells = []
with open(aux_info.lib_filepath) as f:
cell = None
for line in f:
if line.startswith('CELL'):
cell_name = line.split(' ')[1]
cell = Cell()
cell.name = cell_name.strip('\n')
elif line.startswith(' PIN'):
pin = CellPin()
tokens = line.split()
assert len(tokens) >= 3
for i, token in enumerate(tokens):
token = token.strip('\n')
if i == 0:
assert token == 'PIN'
elif i == 1:
pin.name = token
elif i == 2:
pin.dir = token
elif i == 3:
if token == 'CLOCK':
pin.is_clock = True
else:
pin.is_clock = False
if token == 'CTRL':
pin.is_ctrl = True
else:
pin.is_ctrl = False
else:
assert False, 'Unrecognzied pin attribute'
cell.pins.append(pin)
elif line.startswith('END CELL'):
cells.append(cell)
cell = None
if merge_ports:
for i in xrange(len(cells)):
ports = OrderedDict()
for pin in cells[i].pins:
match = index_regex.match(pin.name)
if match:
port_name = match.groupdict()['name']
index = match.groupdict()['index']
if port_name not in ports:
ports[port_name] = []
ports[port_name].append(pin)
else:
if pin.name not in ports:
ports[pin.name] = []
ports[pin.name].append(pin)
cells[i].pins = []
for port_name, pins in ports.iteritems():
#All pins in port must have same: dir/is_clock/is_ctrl
dir = None
is_clock = None
is_ctrl = None
for pin in pins:
if not dir:
dir = pin.dir
is_clock = pin.is_clock
is_ctrl = pin.is_ctrl
else:
assert dir == pin.dir
assert is_clock == pin.is_clock
assert is_ctrl == pin.is_ctrl
port = CellPin()
port.name = port_name
port.dir = dir
port.is_clock = is_clock
port.is_ctrl = is_ctrl
port.num_pins = len(pins)
cells[i].pins.append(port)
#print '#', cells[i].name, port.name, port.num_pins
#for pin in cells[i].pins:
#print '*', cells[i].name, pin.name, pin.num_pins
#for cell in cells:
#for pin in cell.pins:
#print '!', cell.name, pin.name, pin.num_pins
return cells
def parse_nodes(aux_info):
nodes = OrderedDict()
with open(aux_info.nodes_filepath) as f:
for line in f:
inst_name, type = line.split()
assert inst_name is not None
assert type is not None
nodes[inst_name] = type
return nodes
def parse_nets(aux_info):
nets = []
with open(aux_info.nets_filepath) as f:
net = None
for line in f:
if line.startswith('#'):
continue
elif line.startswith('net'):
net_token, net_name, num_pins = line.split()
assert net_name is not None
net = Net()
net.name = net_name
net.pins = []
elif line.startswith('endnet'):
nets.append(net)
net = None
else:
#Assume net pin
inst_name, pin_name = line.split()
assert pin_name is not None
net_pin = NetPin()
net_pin.inst_name = inst_name
net_pin.pin_name = pin_name
net.pins.append(net_pin)
return nets
def parse_scl(aux_info):
grid = None
sites = []
resources = []
with open(aux_info.scl_filepath) as f:
for line in f:
#SITEMAP
if line.startswith('SITEMAP'):
sitemap_token, width, height = line.split()
assert not grid
grid = DeviceGrid()
grid.width = width
grid.height = height
for line in f:
if line.startswith('END SITEMAP'):
break
x, y, site = line.split()
grid.sites.append((x, y, site))
#SITE parsing
elif line.startswith('SITE'):
site_token, site_type = line.split()
site = DeviceSite()
site.name = site_type
for line in f:
if line.startswith('END SITE'):
break
resource, count = line.split()
site.resources.append((resource, count))
sites.append(site)
#RESOURCE parsing
elif line.startswith('RESOURCES'):
for line in f:
if line.startswith('END RESOURCES'):
break
resource_info = line.split()
resource = DeviceResource()
resource.name = resource_info[0]
resource.primitives = resource_info[1:]
resources.append(resource)
return grid, sites, resources
def add_arch_model(root, cell):
assert root.tag == 'architecture'
#Create models if first model
models = root.find('models')
if models is None:
models = ET.SubElement(root, 'models')
#Initialize the current model
model = ET.SubElement(models, 'model')
model.set('name', cell.name)
input_pins = [pin for pin in cell.pins if pin.dir == 'INPUT']
output_pins = [pin for pin in cell.pins if pin.dir == 'OUTPUT']
#Find the clock, assume all ports sequential
clock_name = None
for ipin in input_pins:
if ipin.is_clock:
assert clock_name == None, 'Primitive with multiple clocks'
clock_name = ipin.name
#Create each input pin
input_ports = ET.SubElement(model, 'input_ports')
for ipin in input_pins:
port = ET.SubElement(input_ports, 'port')
port.set('name', ipin.name)
if ipin.is_clock:
port.set('is_clock', '1')
elif clock_name != None:
port.set('clock', clock_name)
#Create each output pin
output_ports = ET.SubElement(model, 'output_ports')
for opin in output_pins:
assert not opin.is_clock
assert not opin.is_ctrl
port = ET.SubElement(output_ports, 'port')
port.set('name', opin.name)
if clock_name != None:
port.set('clock', clock_name)
def add_arch_block(root, site, resources, cells):
#Create complexblocklist if first block
cblist = root.find('complexblocklist')
if cblist is None:
cblist = ET.SubElement(root, 'complexblocklist')
#Create the top-level PB type
pb_type = ET.SubElement(cblist, 'pb_type')
pb_type.set('name', site.name)
for resource, count in site.resources:
matching_resources = [r for r in resources if r.name == resource]
assert len(matching_resources) == 1
child_pb_type = create_resource(matching_resources[0], cells)
child_pb_type.set('num_pb', count)
pb_type.append(child_pb_type)
def create_resource(resource, cells):
pb_type = ET.Element('pb_type')
pb_type.set('name', resource.name)
for prim in resource.primitives:
mode = ET.SubElement(pb_type, 'mode')
mode.set('name', prim)
prim_pb_type = ET.SubElement(mode, 'pb_type')
prim_pb_type.set('name', prim)
prim_pb_type.set('blif_model', '.subckt {}'.format(prim))
matching_cells= [cell for cell in cells if cell.name == prim]
assert len(matching_cells) == 1
cell = matching_cells[0]
inputs = [pin for pin in cell.pins if pin.dir == 'INPUT']
for ipin in inputs:
input = ET.SubElement(prim_pb_type, 'input')
input.set('name', ipin.name)
input.set('num_pins', str(ipin.num_pins))
outputs = [pin for pin in cell.pins if pin.dir == 'OUTPUT']
for opin in outputs:
output = ET.SubElement(prim_pb_type, 'output')
output.set('name', opin.name)
output.set('num_pins', str(opin.num_pins))
return pb_type
def add_arch_grid(root, grid):
layout = root.find('layout')
assert not layout
layout = ET.SubElement(root, 'layout')
fixed_layout = ET.SubElement(layout, 'fixed_layout')
fixed_layout.set('width', grid.width)
fixed_layout.set('height', grid.height)
fixed_layout.set('name', 'ultrascale')
for x, y, site in grid.sites:
single = ET.SubElement(fixed_layout, 'single')
single.set('type', site)
single.set('priority', '1')
single.set('x', x)
single.set('y', y)
def pretty_print_list(f, list, indent=' '):
for i, item in enumerate(list):
print >>f, "{}{}".format(indent, item),
if i == len(list) - 1:
print >>f, ''
else:
print >>f, '\\'
if __name__ == '__main__':
main()