| 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): | |
| bloc = loc2str(loc) | |
| ioname = self.get_io_name(loc) | |
| assign = "" | |
| direction = self.get_io_config(parameters) | |
| print(direction, bloc, ioname, 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[loc] | |
| if 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(): | |
| 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(): | |
| qcf += f'place {io.name} {self.io_to_fbio[io.ioloc]}\n' | |
| return qcf |