| 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 |