blob: 57563d57e55d09370fdca18be7802536de83511f [file] [log] [blame] [edit]
import re
from collections import namedtuple, defaultdict
from data_structs import PinDirection
Element = namedtuple('Element', 'loc type name ios')
Wire = namedtuple('Wire', 'srcloc name inverted')
VerilogIO = namedtuple('VerilogIO', 'name direction ioloc')
def loc2str(loc):
return "X" + str(loc.x) + "Y" + str(loc.y)
class VModule(object):
'''Represents a Verilog module for QLAL4S3B FASM'''
def __init__(
self,
vpr_tile_grid,
vpr_tile_types,
cells_library,
pcf_data,
belinversions,
interfaces,
designconnections,
org_loc_map,
cand_map,
inversionpins,
io_to_fbio,
useinversionpins=True
):
'''Prepares initial structures.
Refer to fasm2bels.py for input description.
'''
self.vpr_tile_grid = vpr_tile_grid
self.vpr_tile_types = vpr_tile_types
self.cells_library = cells_library
self.pcf_data = pcf_data
self.belinversions = belinversions
self.interfaces = interfaces
self.designconnections = designconnections
self.org_loc_map = org_loc_map
self.cand_map = cand_map
self.inversionpins = inversionpins
self.useinversionpins = useinversionpins
self.io_to_fbio = io_to_fbio
# dictionary holding inputs, outputs
self.ios = {}
# dictionary holding declared wires (wire value;)
self.wires = {}
# dictionary holding Verilog elements
self.elements = defaultdict(dict)
# dictionary holding assigns (assign key = value;)
self.assigns = {}
# helper representing last input id
self.last_input_id = 0
# helper representing last output id
self.last_output_id = 0
self.qlal4s3bmapping = {
'LOGIC': 'logic_cell_macro',
'ASSP': 'qlal4s3b_cell_macro',
'BIDIR': 'gpio_cell_macro',
'RAM': 'ram8k_2x1_cell_macro',
'MULT': 'qlal4s3_mult_cell_macro',
'GMUX': 'gclkbuff',
'QMUX': 'qhsckbuff',
'CLOCK': 'ckpad',
'inv': 'inv'
}
self.qlal4s3_pinmap = {
"ckpad": {
"IP": "P",
"IC": "Q",
},
"gclkbuff": {
"IC": "A",
"IZ": "Z",
},
"qhsckbuff": {
"HSCKIN": "A",
"IZ": "Z"
},
}
def group_vector_signals(self, signals, io=False):
# IOs beside name, have also direction, convert them to format
# we can process
if io:
orig_ios = signals
ios = dict()
for s in signals:
id = Wire(s.name, 'io', False)
ios[id] = s.name
signals = ios
vectors = dict()
new_signals = dict()
array = re.compile(
r'(?P<varname>[a-zA-Z_][a-zA-Z_0-9$]+)\[(?P<arrindex>[0-9]+)\]'
)
# first find the vectors
for signalid in signals:
match = array.match(signals[signalid])
if match:
varname = match.group('varname')
arrayindex = int(match.group('arrindex'))
if varname not in vectors:
vectors[varname] = dict()
vectors[varname]['max'] = 0
vectors[varname]['min'] = 0
if arrayindex > vectors[varname]['max']:
vectors[varname]['max'] = arrayindex
if arrayindex < vectors[varname]['min']:
vectors[varname]['min'] = arrayindex
# if signal is not a part of a vector leave it
else:
new_signals[signalid] = signals[signalid]
# add vectors to signals dict
for vec in vectors:
name = '[{max}:{min}] {name}'.format(
max=vectors[vec]['max'], min=vectors[vec]['min'], name=vec
)
id = Wire(name, 'vector', False)
new_signals[id] = name
if io:
# we need to restore the direction info
new_ios = list()
for s in new_signals:
signalname = new_signals[s].split()
signalname = signalname[-1]
io = [
x.direction
for x in orig_ios
if x.name.startswith(signalname)
]
direction = io[0]
new_ios.append((direction, new_signals[s]))
return new_ios
else:
return new_signals
def group_array_values(self, parameters: dict):
'''Groups pin names that represent array indices.
Parameters
----------
parameters: dict
A dictionary holding original parameters
Returns
-------
dict: parameters with grouped array indices
'''
newparameters = dict()
arraydst = re.compile(
r'(?P<varname>[a-zA-Z_][a-zA-Z_0-9$]+)\[(?P<arrindex>[0-9]+)\]'
)
for dst, src in parameters.items():
match = arraydst.match(dst)
if match:
varname = match.group('varname')
arrindex = int(match.group('arrindex'))
if varname not in newparameters:
newparameters[varname] = {arrindex: src}
else:
newparameters[varname][arrindex] = src
else:
newparameters[dst] = src
return newparameters
def form_simple_assign(self, loc, parameters):
ioname = self.get_io_name(loc)
assign = ""
direction = self.get_io_config(parameters)
if direction == 'input':
assign = " assign {} = {};".format(parameters["IZ"], ioname)
elif direction == 'output':
assign = " assign {} = {};".format(ioname, parameters["OQI"])
elif direction is None:
pass
else:
assert False, "Unknown IO configuration"
return assign
def form_verilog_element(self, loc, typ: str, name: str, parameters: dict):
'''Creates an entry representing single Verilog submodule.
Parameters
----------
loc: Loc
Cell coordinates
typ: str
Cell type
name: str
Name of the submodule
parameters: dict
Map from input pin to source wire
Returns
-------
str: Verilog entry
'''
if typ == 'BIDIR':
# We do not emit the BIDIR cell for non inout IOs
direction = self.get_io_config(parameters)
if direction is None:
return ""
elif direction != 'inout':
return self.form_simple_assign(loc, parameters)
params = []
moduletype = self.qlal4s3bmapping[typ]
pin_map = self.qlal4s3_pinmap.get(moduletype, dict())
result = f' {moduletype} {name} ('
fixedparameters = self.group_array_values(parameters)
# get inputs, strip vector's pin indexes
input_pins = [
pin.name.split('[')[0]
for pin in self.cells_library[typ].pins
if pin.direction == PinDirection.INPUT
]
dummy_wires = []
for inpname, inp in fixedparameters.items():
mapped_inpname = pin_map.get(inpname, inpname)
if isinstance(inp, dict):
arr = []
dummy_wire = f'{moduletype}_{name}_{inpname}'
max_dummy_index = 0
need_dummy = False
maxindex = max([val for val in inp.keys()])
for i in reversed(range(maxindex + 1)):
if i not in inp:
# do not assign constants to outputs
if inpname in input_pins:
arr.append("1'b0")
else:
if i > max_dummy_index:
max_dummy_index = i
arr.append("{}[{}]".format(dummy_wire, i))
need_dummy = True
else:
arr.append(inp[i])
arrlist = ', '.join(arr)
params.append(f'.{mapped_inpname}({{{arrlist}}})')
if need_dummy:
dummy_wires.append(
f' wire [{max_dummy_index}:0] {dummy_wire};'
)
else:
params.append(f'.{mapped_inpname}({inp})')
if self.useinversionpins:
if typ in self.inversionpins:
for toinvert, inversionpin in self.inversionpins[typ].items():
if toinvert in self.belinversions[loc][typ]:
params.append(f".{inversionpin}(1'b1)")
else:
params.append(f".{inversionpin}(1'b0)")
# handle BIDIRs and CLOCKs
if typ in ['CLOCK', 'BIDIR']:
ioname = self.get_io_name(loc)
moduletype = self.qlal4s3bmapping[typ]
pin_map = self.qlal4s3_pinmap.get(moduletype, dict())
params.append(".{}({})".format(pin_map.get("IP", "IP"), ioname))
result += f',\n{" " * len(result)}'.join(sorted(params)) + ');\n'
wires = ''
for wire in dummy_wires:
wires += f'\n{wire}'
result = wires + '\n\n' + result
return result
@staticmethod
def get_element_name(type, loc):
'''Forms element name from its type and FASM feature name.'''
return f'{type}_X{loc.x}_Y{loc.y}'
@staticmethod
def get_element_type(type):
match = re.match(r"(?P<type>[A-Za-z]+)(?P<index>[0-9]+)?", type)
assert match is not None, type
return match.group("type")
def get_bel_type_and_connections(self, loc, connections, direction):
'''For a given connection list returns a dictionary
with bel types and connections to them
'''
cells = self.vpr_tile_grid[loc].cells
if type(connections) == str:
inputnames = [connections]
# convert connections to dict
connections = dict()
connections[inputnames[0]] = inputnames[0]
else:
inputnames = [name for name in connections.keys()]
# Some switchbox inputs are named like "<cell><cell_index>_<pin>"
# Make a list of them for compatison in the following step.
cell_input_names = defaultdict(lambda: [])
for name in inputnames:
fields = name.split("_", maxsplit=1)
if len(fields) == 2:
cell, pin = fields
cell_input_names[cell].append(pin)
used_cells = []
for cell in cells:
cell_name = "{}{}".format(cell.type, cell.index)
cellpins = [
pin.name
for pin in self.cells_library[cell.type].pins
if pin.direction == direction
]
# check every connection pin if it has
for pin in cellpins:
# Cell name and pin name match
if cell_name in cell_input_names:
if pin in cell_input_names[cell_name]:
used_cells.append(cell)
break
# The pin name matches exactly the specified input name
if pin in inputnames:
used_cells.append(cell)
break
# assing connection to a cell
cell_connections = {}
for cell in used_cells:
cell_name = "{}{}".format(cell.type, cell.index)
cell_connections[cell_name] = dict()
cellpins = [
pin.name
for pin in self.cells_library[cell.type].pins
if pin.direction == direction
]
for key in connections.keys():
if key in cellpins:
cell_connections[cell_name][key] = connections[key]
# Some switchbox inputs are named like
# "<cell><cell_index>_<pin>". Break down the name and check.
fields = key.split("_", maxsplit=1)
if len(fields) == 2:
key_cell, key_pin = fields
if key_cell == cell_name and key_pin in cellpins:
cell_connections[cell_name][key_pin] = connections[key]
return cell_connections
def new_io_name(self, direction):
'''Creates a new IO name for a given direction.
Parameters
----------
direction: str
Direction of the IO, can be 'input' or 'output'
'''
# TODO add support for inout
assert direction in ['input', 'output', 'inout']
if direction == 'output':
name = f'out_{self.last_output_id}'
self.last_output_id += 1
elif direction == 'input':
name = f'in_{self.last_input_id}'
self.last_input_id += 1
else:
pass
return name
def get_wire(self, loc, wire, inputname):
'''Creates or gets an existing wire for a given source.
Parameters
----------
loc: Loc
Location of the destination cell
wire: tuple
A tuple of location of the source cell and source pin name
inputname: str
A name of the destination pin
Returns
-------
str: wire name
'''
isoutput = self.vpr_tile_grid[loc].type == 'SYN_IO'
if isoutput:
# outputs are never inverted
inverted = False
else:
# determine if inverted
inverted = (
inputname in self.belinversions[loc][
self.vpr_tile_grid[loc].type]
)
wireid = Wire(wire[0], wire[1], inverted)
if wireid in self.wires:
# if wire already exists, use it
return self.wires[wireid]
# first create uninverted wire
uninvertedwireid = Wire(wire[0], wire[1], False)
if uninvertedwireid in self.wires:
# if wire already exists, use it
wirename = self.wires[uninvertedwireid]
else:
srcname = self.vpr_tile_grid[wire[0]].name
type_connections = self.get_bel_type_and_connections(
wire[0], wire[1], PinDirection.OUTPUT
)
# there should be only one type here
srctype = [type for type in type_connections.keys()][0]
srconame = wire[1]
if srctype == 'SYN_IO':
# if source is input, use its name
if wire[0] not in self.ios:
self.ios[wire[0]] = VerilogIO(
name=self.new_io_name('input'),
direction='input',
ioloc=wire[0]
)
assert self.ios[wire[0]].direction == 'input'
wirename = self.ios[wire[0]].name
else:
# form a new wire name
wirename = f'{srcname}_{srconame}'
if srctype not in self.elements[wire[0]]:
# if the source element does not exist, create it
self.elements[wire[0]][srctype] = Element(
wire[0], self.get_element_type(srctype),
self.get_element_name(srctype, wire[0]),
{srconame: wirename}
)
else:
# add wirename to the existing element
self.elements[wire[0]][srctype].ios[srconame] = wirename
if not isoutput and srctype != 'SYN_IO':
# add wire
self.wires[uninvertedwireid] = wirename
elif isoutput:
# add assign to output
self.assigns[self.ios[loc].name] = wirename
if not inverted or (
self.useinversionpins and
inputname in self.inversionpins[self.vpr_tile_grid[loc].type]):
# if not inverted or we're not inverting, just finish
return wirename
# else create an inverted and wire for it
invertername = f'{wirename}_inverter'
invwirename = f'{wirename}_inv'
inverterios = {'Q': invwirename, 'A': wirename}
inverterelement = Element(wire[0], 'inv', invertername, inverterios)
self.elements[wire[0]]['inv'] = inverterelement
invertedwireid = Wire(wire[0], wire[1], True)
self.wires[invertedwireid] = invwirename
return invwirename
def parse_bels(self):
'''Converts BELs to Verilog-like structures.'''
# TODO add support for direct input-to-output
# first parse outputs to create wires for them
# parse outputs first to properly handle namings
for currloc, connections in self.designconnections.items():
type_connections = self.get_bel_type_and_connections(
currloc, connections, PinDirection.OUTPUT
)
for currtype, connections in type_connections.items():
currname = self.get_element_name(currtype, currloc)
outputs = {}
# Check each output
for output_name, (
loc,
wire,
) in connections.items():
# That wire is connected to something. Skip processing
# of the cell here
if loc is not None:
continue
# Connect the global wire
outputs[output_name] = wire
# No outputs connected, don't add.
if not len(outputs):
continue
# If Element does not exist, create it
if currtype not in self.elements[currloc]:
self.elements[currloc][currtype] = Element(
currloc, self.get_element_type(currtype), currname,
outputs
)
# Else update IOs
else:
self.elements[currloc][currtype].ios.update(outputs)
# process of BELs
for currloc, connections in self.designconnections.items():
# Extract type and form name for the BEL
# Current location may be a multi cell location.
# Split the connection list into a to a set of connections
# for each used cell type
type_connections = self.get_bel_type_and_connections(
currloc, connections, PinDirection.INPUT
)
for currtype in type_connections:
currname = self.get_element_name(currtype, currloc)
connections = type_connections[currtype]
inputs = {}
# form all inputs for the BEL
for inputname, wire in connections.items():
if wire[1] == 'VCC':
inputs[inputname] = "1'b1"
continue
elif wire[1] == 'GND':
inputs[inputname] = "1'b0"
continue
elif wire[1].startswith("CAND"):
dst = (currloc, inputname)
dst = self.org_loc_map.get(dst, dst)
if dst[0] in self.cand_map:
inputs[inputname] = self.cand_map[dst[0]][wire[1]]
continue
srctype = self.vpr_tile_grid[wire[0]].type
srctype_cells = self.vpr_tile_types[srctype].cells
if len(set(srctype_cells).intersection(set(
['BIDIR', 'LOGIC', 'ASSP', 'RAM', 'MULT']))) > 0:
# FIXME handle already inverted pins
# TODO handle inouts
wirename = self.get_wire(currloc, wire, inputname)
inputs[inputname] = wirename
else:
raise Exception(
'Not supported cell type {}'.format(srctype)
)
if currtype not in self.elements[currloc]:
# If Element does not exist, create it
self.elements[currloc][currtype] = Element(
currloc, self.get_element_type(currtype), currname,
inputs
)
else:
# else update IOs
self.elements[currloc][currtype].ios.update(inputs)
# Prune BELs that do not drive anythin (have all outputs disconnected)
for loc, elements in list(self.elements.items()):
for type, element in list(elements.items()):
# Handle IO cells
if element.type in ['CLOCK', 'BIDIR', 'SDIOMUX']:
if element.type == "CLOCK":
direction = "input"
else:
direction = self.get_io_config(element.ios)
# Remove the cell if none is connected
if direction is None:
del elements[type]
# Handle non-io cells
else:
# Get connected pin names and output pin names
connected_pins = set(element.ios.keys())
output_pins = set(
[
pin.name.split('[')[0]
for pin in self.cells_library[element.type].pins
if pin.direction == PinDirection.OUTPUT
]
)
# Remove the cell if none is connected
if not len(connected_pins & output_pins):
del elements[type]
# Prune the whole location
if not len(elements):
del self.elements[loc]
def get_io_name(self, loc):
# default pin name
name = loc2str(loc) + '_inout'
# check if we have the original name for this io
if self.pcf_data is not None:
pin = self.io_to_fbio.get(loc, None)
if pin is not None and pin in self.pcf_data:
name = self.pcf_data[pin]
name = name.replace('(', '[')
name = name.replace(')', ']')
return name
def get_io_config(self, ios):
# decode direction
# direction is configured by routing 1 or 0 to certain inputs
if 'IE' in ios:
output_en = ios['IE'] != "1'b0"
else:
# outputs is enabled by default
output_en = True
if 'INEN' in ios:
input_en = ios['INEN'] != "1'b0"
else:
# inputs are disabled by default
input_en = False
if input_en and output_en:
direction = 'inout'
elif input_en:
direction = 'input'
elif output_en:
direction = 'output'
else:
direction = None
# Output unrouted. Discard
if direction == 'input':
if 'IZ' not in ios:
return None
return direction
def generate_ios(self):
'''Generates IOs and their wires
Returns
-------
None
'''
for eloc, locelements in self.elements.items():
for element in locelements.values():
if element.type in ['CLOCK', 'BIDIR', 'SDIOMUX']:
if element.type == "CLOCK":
direction = "input"
else:
direction = self.get_io_config(element.ios)
# Add the input if used
if direction is not None:
name = self.get_io_name(eloc)
self.ios[eloc] = VerilogIO(
name=name, direction=direction, ioloc=eloc
)
def generate_verilog(self):
'''Creates Verilog module
Returns
-------
str: A Verilog module for given BELs
'''
ios = ''
wires = ''
assigns = ''
elements = ''
self.generate_ios()
if len(self.ios) > 0:
sortedios = sorted(
self.ios.values(), key=lambda x: (x.direction, x.name)
)
grouped_ios = self.group_vector_signals(sortedios, True)
ios = '\n '
ios += ',\n '.join([f'{x[0]} {x[1]}' for x in grouped_ios])
grouped_wires = self.group_vector_signals(self.wires)
if len(grouped_wires) > 0:
wires += '\n'
for wire in grouped_wires.values():
wires += f' wire {wire};\n'
if len(self.assigns) > 0:
assigns += '\n'
for dst, src in self.assigns.items():
assigns += f' assign {dst} = {src};\n'
if len(self.elements) > 0:
for eloc, locelements in self.elements.items():
for element in locelements.values():
if element.type != 'SYN_IO':
elements += '\n'
elements += self.form_verilog_element(
eloc, element.type, element.name, element.ios
)
verilog = (
f'module top ({ios});\n'
f'{wires}'
f'{assigns}'
f'{elements}'
f'\n'
f'endmodule'
)
return verilog
def generate_pcf(self):
pcf = ''
for io in self.ios.values():
if io.ioloc in self.io_to_fbio:
pcf += f'set_io {io.name} {self.io_to_fbio[io.ioloc]}\n'
return pcf
def generate_qcf(self):
qcf = '#[Fixed Pin Placement]\n'
for io in self.ios.values():
if io.ioloc in self.io_to_fbio:
qcf += f'place {io.name} {self.io_to_fbio[io.ioloc]}\n'
return qcf