def load_other_cells(xml_placement, cellgrid, cells_library): # Loop over XML entries for xml in xml_placement: # Got a "Cell" tag if xml.tag == "Cell": cell_name = xml.get("name") cell_type = xml.get("type") assert cell_type in cells_library, ( cell_type, cell_name, ) # Cell matrix xml_matrices = [x for x in xml if x.tag.startswith("Matrix")] for xml_matrix in xml_matrices: x0 = int(xml_matrix.get("START_COLUMN")) nx = int(xml_matrix.get("COLUMNS")) y0 = int(xml_matrix.get("START_ROW")) ny = int(xml_matrix.get("ROWS")) for j in range(ny): for i in range(nx): loc = Loc(x0 + i, y0 + j, 0) cellgrid[loc].append( Cell( type=cell_type, index=None, name=cell_name, alias=None, )) # A single cell if len(xml_matrices) == 0: x = int(xml.get("column")) y = int(xml.get("row")) loc = Loc(x, y, 0) alias = xml.get("Alias", None) cellgrid[loc].append( Cell( type=cell_type, index=None, name=cell_name, alias=alias, )) # Got something else, parse recursively else: load_other_cells(xml, cellgrid, cells_library)
def parse_fasm_lines(self, fasmlines): '''Parses FASM lines. Parameters ---------- fasmlines: list A list of FasmLine objects ''' loctyp = re.compile( r'^X(?P<x>[0-9]+)Y(?P<y>[0-9]+)\.(?P<type>[A-Z]+[0-4]?)\.(?P<signature>.*)$' ) # noqa: E501 for line in fasmlines: if not line.set_feature: continue match = loctyp.match(line.set_feature.feature) if not match: raise self.Fasm2BelsException( f'FASM features have unsupported format: {line.set_feature}' ) # noqa: E501 loc = Loc(x=int(match.group('x')), y=int(match.group('y')), z=0) typ = match.group('type') feature = Feature(loc=loc, typ=typ, signature=match.group('signature'), value=line.set_feature.value) self.featureparsers[typ](feature)
def create_column_clock_tracks(graph, clock_cells, quadrants): """ This function adds tracks for clock column routes. It returns a map of "assess points" to that tracks to be used by switchbox connections. """ CAND_RE = re.compile( r"^(?P<name>CAND[0-4])_(?P<quad>[A-Z]+)_(?P<col>[0-9]+)$") # Get segment id and switch id segment_id = graph.get_segment_id_from_name("clock") switch_id = graph.get_delayless_switch_id() # Process CAND cells cand_node_map = {} for cell in clock_cells.values(): # A clock column is defined by a CAND cell if cell.type != "CAND": continue # Get index and quadrant match = CAND_RE.match(cell.name) if not match: continue cand_name = match.group("name") cand_quad = match.group("quad") quadrant = quadrants[cand_quad] # Add track chains going upwards and downwards from the CAND cell up_entry_node, _, up_node_map = add_track_chain( graph, "Y", cell.loc.x, cell.loc.y, quadrant.y0, segment_id, switch_id) dn_entry_node, _, dn_node_map = add_track_chain( graph, "Y", cell.loc.x, cell.loc.y + 1, quadrant.y1, segment_id, switch_id) # Connect entry nodes cand_entry_node = up_entry_node add_edge(graph, cand_entry_node.id, dn_entry_node.id, switch_id) # Join node maps node_map = {**up_node_map, **dn_node_map} # Populate the global clock network to switchbox access map for y, node in node_map.items(): loc = Loc(x=cell.loc.x, y=y, z=0) if cand_name not in cand_node_map: cand_node_map[cand_name] = {} cand_node_map[cand_name][loc] = node return cand_node_map
def fixup_cand_loc(vpr_loc, phy_loc): """ Fixes up location of a CAND cell so that all of them occupy the same row. Returns the cell location in VPR coordinates """ # Even, don't modify if not (phy_loc.y % 2): return vpr_loc # Odd, shift down by 1 return Loc(vpr_loc.x, vpr_loc.y + 1, vpr_loc.z)
def node_joint_location(node_a, node_b): """ Given two VPR nodes returns a location of the point where they touch each other. """ loc_a1 = Loc(node_a.loc.x_low, node_a.loc.y_low, 0) loc_a2 = Loc(node_a.loc.x_high, node_a.loc.y_high, 0) loc_b1 = Loc(node_b.loc.x_low, node_b.loc.y_low, 0) loc_b2 = Loc(node_b.loc.x_high, node_b.loc.y_high, 0) if loc_a1 == loc_b1: return loc_a1 if loc_a1 == loc_b2: return loc_a1 if loc_a2 == loc_b1: return loc_a2 if loc_a2 == loc_b2: return loc_a2 assert False, (node_a, node_b)
def get_qmux_for_cand(self, cand, loc): ''' Returns a QMUX cell and its location that drives the given CAND cell. Parameters ---------- cand: str The CAND cell name loc: Loc The CAND location Returns ------- Tuple: A tuple holding (loc, cell) ''' connections = [ c for c in self.connections if c.dst.type == ConnectionType.CLOCK ] for connection in connections: # Only to a CAND at the given location # Note: Check also the row above. CAND cells are located in two # rows but with fasm features everything gets aligned to even rows dst = connection.dst if (dst.loc != loc and dst.loc != Loc(loc.x, loc.y - 1, loc.z)) or \ "CAND" not in dst.pin: continue # CAND cells are named "CAND<index>_<quad>_<column>". cell, pin = dst.pin.split(".", maxsplit=1) match = re.match( r"CAND(?P<idx>[0-9]+)_(?P<quad>[A-Z]+)_(?P<col>[0-9]+)", cell) if match is None: continue # This is not for the given CAND if cand != "CAND{}".format(match.group("idx")): continue # QMUX cells are named "QMUX_<quad><index>". cell, pin = connection.src.pin.split(".", maxsplit=1) match = re.match(r"QMUX_(?P<quad>[A-Z]+)(?P<idx>[0-9]+)", cell) if match is None: continue # Return the QMUX and its location return "QMUX{}".format(match.group("idx")), connection.src.loc # None found return None, None
def load_logic_cells(xml_placement, cellgrid, cells_library): # Load "LOGIC" tiles xml_logic = xml_placement.find("LOGIC") assert xml_logic is not None exceptions = set() xml_exceptions = xml_logic.find("EXCEPTIONS") if xml_exceptions is not None: for xml in xml_exceptions: tag = xml.tag.upper() # FIXME: Is this connect decoding of those werid loc specs? x = 1 + ord(tag[0]) - ord("A") y = 1 + int(tag[1:]) exceptions.add(Loc(x=x, y=y, z=0)) xml_logicmatrix = xml_logic.find("LOGICMATRIX") assert xml_logicmatrix is not None x0 = int(xml_logicmatrix.get("START_COLUMN")) nx = int(xml_logicmatrix.get("COLUMNS")) y0 = int(xml_logicmatrix.get("START_ROW")) ny = int(xml_logicmatrix.get("ROWS")) for j in range(ny): for i in range(nx): loc = Loc(x0 + i, y0 + j, 0) if loc in exceptions: continue cell_type = "LOGIC" assert cell_type in cells_library, cell_type cellgrid[loc].append( Cell(type=cell_type, index=None, name=cell_type, alias=None))
def resolve_cand(self, qmux_map): '''Resolves CAND cells, creates the cand_map map. Parameters ---------- qmux_map: Dict A map of locations and CAND names to their driving QMUXes. Returns ------- None ''' # Process CAND for loc, all_features in self.colclk_data.items(): for cand, features in all_features.items(): hilojoint = False enjoint = False for feature in features: if feature.signature == "I_hilojoint": hilojoint = bool(feature.value) if feature.signature == "I_enjoint": enjoint = bool(feature.value) # TODO: Do not support dynamically enabled CANDs for now. assert enjoint is False, "Dynamically enabled CANDs are not supported yet" # Statically disabled, skip this one if hilojoint is False: continue # Find a QMUX driving this CAND cell qmux_cell, qmux_loc = self.get_qmux_for_cand(cand, loc) assert qmux_cell is not None, (cand, loc) # The QMUX is not active, skip this one if qmux_loc not in qmux_map: continue # Get the wire wire = qmux_map[qmux_loc][qmux_cell] # Populate the column clock to switchbox connection map quadrant = get_quadrant_for_loc(loc, self.quadrants) for y in range(quadrant.y0, quadrant.y1 + 1): sb_loc = Loc(loc.x, y, 0) self.cand_map[sb_loc][cand] = wire
def populate_switchboxes(xml_sbox, switchbox_grid): """ Assings each tile in the grid its switchbox type. """ xmin = int(xml_sbox.attrib["ColStartNum"]) xmax = int(xml_sbox.attrib["ColEndNum"]) ymin = int(xml_sbox.attrib["RowStartNum"]) ymax = int(xml_sbox.attrib["RowEndNum"]) for y, x in itertools.product(range(ymin, ymax + 1), range(xmin, xmax + 1)): loc = Loc(x, y, 0) assert loc not in switchbox_grid, loc switchbox_grid[loc] = xml_sbox.tag
def yield_locs_and_maps(): """ Yields locations and wire mappings associated with it. """ RE_LOC = re.compile(r"^(Row|Col)_([0-9]+)_([0-9]+)$") # Rows xml_rows = [e for e in xml_root if e.tag.startswith("Row_")] for xml_row in xml_rows: # Decode row range match = RE_LOC.match(xml_row.tag) assert match is not None, xml_row.tag row_beg = int(xml_row.attrib["RowStartNum"]) row_end = int(xml_row.attrib["RowEndNum"]) assert row_beg == int(match.group(2)), \ (xml_row.tag, row_beg, row_end) assert row_end == int(match.group(3)), \ (xml_row.tag, row_beg, row_end) # Columns xml_cols = [e for e in xml_row if e.tag.startswith("Col_")] for xml_col in xml_cols: # Decode column range match = RE_LOC.match(xml_col.tag) assert match is not None, xml_col.tag col_beg = int(xml_col.attrib["ColStartNum"]) col_end = int(xml_col.attrib["ColEndNum"]) assert col_beg == int(match.group(2)), \ (xml_col.tag, col_beg, col_end) assert col_end == int(match.group(3)), \ (xml_col.tag, col_beg, col_end) # Wire maps xml_maps = [e for e in xml_col if e.tag.startswith("Stage_")] # Yield wire maps for each location for y in range(row_beg, row_end + 1): for x in range(col_beg, col_end + 1): yield (Loc(x=x, y=y, z=0), xml_maps)
def process_switchbox_grid( phy_switchbox_grid, loc_map, grid_offset, grid_limit=None ): """ Processes the switchbox grid """ fwd_loc_map = loc_map.fwd bwd_loc_map = loc_map.bwd def add_loc_map(phy_loc, vpr_loc): if phy_loc in fwd_loc_map: assert fwd_loc_map[phy_loc] == vpr_loc, (phy_loc, vpr_loc) else: fwd_loc_map[phy_loc] = vpr_loc if vpr_loc in bwd_loc_map: assert bwd_loc_map[vpr_loc] == phy_loc, (phy_loc, vpr_loc) else: bwd_loc_map[vpr_loc] = phy_loc # Remap locations vpr_switchbox_grid = {} for phy_loc, switchbox_type in phy_switchbox_grid.items(): # Limit the grid range if not is_loc_within_limit(phy_loc, grid_limit): continue # compute VPR grid location vpr_loc = Loc( x=phy_loc.x + grid_offset[0], y=phy_loc.y + grid_offset[1], z=0 ) # Place the switchbox vpr_switchbox_grid[vpr_loc] = switchbox_type # Add location correspondence add_loc_map(phy_loc, vpr_loc) return vpr_switchbox_grid, LocMap(fwd=fwd_loc_map, bwd=bwd_loc_map),
def resolve_hops(self): '''Resolves remaining hop wires. It determines the absolute input for the given pin by resolving hop wires and adds those final connections to the design connections. ''' for loc, conns in self.designconnections.items(): for pin, source in conns.items(): hop = get_name_and_hop(source) tloc = loc while hop[1] is not None: tloc = Loc(tloc[0] + hop[1][0], tloc[1] + hop[1][1], 0) # in some cases BEL is distanced from a switchbox, in those # cases the hop will not point to another hop. We should # simply return the pin here in the correct location if hop[0] in self.designhops[tloc]: hop = get_name_and_hop(self.designhops[tloc][hop[0]]) else: hop = (hop[0], None) self.designconnections[loc][pin] = (tloc, hop[0])
def process_switchbox(self, loc, switchbox, features): '''Processes all switchboxes and extract hops from connections. The function extracts final connections from inputs to outputs, and hops into separate structures for further processing. Parameters ---------- loc: Loc location of the current switchbox switchbox: Switchbox a switchbox features: list list of features regarding given switchbox ''' routes = self.decode_switchbox(switchbox, features) for k, v in routes.items(): if v is not None: if re.match('[VH][0-9][LRBT][0-9]', k): self.designhops[Loc(loc.x, loc.y, 0)][k] = v else: self.designconnections[loc][k] = v
def parse_cell(xml_cell, quadrant=None): """ Parses a "Cell" tag inside "CLOCK_NETWORK" """ NON_PIN_TAGS = ("name", "type", "row", "column") cell_loc = Loc(x=int(xml_cell.attrib["column"]), y=int(xml_cell.attrib["row"]), z=0) # Get the cell's pinmap pin_map = { k: v for k, v in xml_cell.attrib.items() if k not in NON_PIN_TAGS } # Return the cell return ClockCell(type=xml_cell.attrib["type"], name=xml_cell.attrib["name"], loc=cell_loc, quadrant=quadrant, pin_map=pin_map)
def process_tilegrid( tile_types, tile_grid, clock_cells, cells_library, grid_size, grid_offset, grid_limit=None ): """ Processes the tilegrid. May add/remove tiles. Returns a new one. """ vpr_tile_grid = {} fwd_loc_map = {} bwd_loc_map = {} ram_blocks = [] mult_blocks = [] vpr_clock_cells = {} def add_loc_map(phy_loc, vpr_loc): fwd_loc_map[phy_loc] = vpr_loc bwd_loc_map[vpr_loc] = phy_loc # Add a fake constant connector pin to LOGIC tile type tile_type = tile_types["LOGIC"] tile_type.fake_const_pin = True tile_type.make_pins(cells_library) # Generate the VPR tile grid for phy_loc, tile in tile_grid.items(): # Limit the grid range if not is_loc_within_limit(phy_loc, grid_limit): continue vpr_loc = Loc( x=phy_loc.x + grid_offset[0], y=phy_loc.y + grid_offset[1], z=0 ) # If the tile contains QMUX or CAND then strip it. Possibly create a # new tile type. tile_type = tile_types[tile.type] if "QMUX" in tile_type.cells or "CAND" in tile_type.cells: # Store the stripped cells for cell in tile.cells: if cell.type in ["QMUX", "CAND"]: # Find it in the physical clock cell list if cell.name not in clock_cells: print( "WARNING: Clock cell '{}' not on the clock cell list!" .format(cell.name) ) continue # Relocate CAND cells so that they occupy only even rows if cell.type == "CAND": cell_loc = fixup_cand_loc(vpr_loc, phy_loc) else: cell_loc = vpr_loc # Get the orignal cell clock_cell = clock_cells[cell.name] pin_map = clock_cell.pin_map # If the cell is QMUX then extend its pin map with # QCLKIN0 and QCLKIN1 pins that are not present in the # techfile. if clock_cell.type == "QMUX": gmux_base = int(pin_map["QCLKIN0"].rsplit("_")[1]) for i in [1, 2]: key = "QCLKIN{}".format(i) val = "GMUX_{}".format((gmux_base + i) % 5) pin_map[key] = val # Add the cell clock_cell = ClockCell( type=clock_cell.type, name=clock_cell.name, loc=cell_loc, quadrant=clock_cell.quadrant, pin_map=pin_map ) vpr_clock_cells[clock_cell.name] = clock_cell # Strip the cells tile = strip_cells( tile, ["QMUX", "CAND"], tile_types, cells_library ) if tile is None: continue # The tile contains a BIDIR or CLOCK cell. it is an IO tile tile_type = tile_types[tile.type] if "BIDIR" in tile_type.cells or "CLOCK" in tile_type.cells: # For the BIDIR cell create a synthetic tile if "BIDIR" in tile_type.cells: assert tile_type.cells["BIDIR"] == 1 cells = [c for c in tile.cells if c.type == "BIDIR"] new_type = make_tile_type(cells, cells_library, tile_types) add_loc_map(phy_loc, vpr_loc) vpr_tile_grid[vpr_loc] = Tile( type=new_type.type, name=tile.name, cells=cells ) # For the CLOCK cell create a synthetic tile if "CLOCK" in tile_type.cells: assert tile_type.cells["CLOCK"] == 1 cells = [c for c in tile.cells if c.type == "CLOCK"] new_type = make_tile_type(cells, cells_library, tile_types) # If the tile has a BIDIR cell then place the CLOCK tile in a # free location next to the original one. if "BIDIR" in tile_type.cells: for ox, oy in ((-1, 0), (+1, 0), (0, -1), (0, +1)): test_loc = Loc(x=phy_loc.x + ox, y=phy_loc.y + oy, z=0) if is_loc_free(test_loc, tile_grid): new_loc = Loc( x=vpr_loc.x + ox, y=vpr_loc.y + oy, z=vpr_loc.z ) break else: assert False, ( "No free location to place CLOCK tile", vpr_loc ) # Don't move else: new_loc = vpr_loc # Add only the backward location correspondence for CLOCK tile bwd_loc_map[new_loc] = phy_loc vpr_tile_grid[new_loc] = Tile( type=new_type.type, name=tile.name, cells=cells ) # Mults and RAMs occupy multiple cells # We'll create a synthetic tile with a single cell for each # RAM and MULT block if "RAM" in tile_type.cells or "MULT" in tile_type.cells: for cell in tile.cells: # Check if the current location is not taken # this could happen because RAM and MULTS share # the same location. General rule here is that # we create a synthetic Tile/Cell in the first # available neighboring location of the original block of cells if cell.type == 'RAM': cells_set = ram_blocks elif cell.type == 'MULT': cells_set = mult_blocks else: continue # Find free location in the physical tile grid close to the # original one. Once found, convert it to location in the # VPR tile grid. for ox, oy in ((0, 0), (0, -1), (0, +1), (-1, 0), (+1, 0)): test_loc = Loc(x=phy_loc.x + ox, y=phy_loc.y + oy, z=0) if is_loc_free(test_loc, tile_grid): new_loc = Loc( x=vpr_loc.x + ox, y=vpr_loc.y + oy, z=vpr_loc.z ) break else: assert False, "No free location to place {} tile".format( cell.type ) # The VPR location is already occupied. Probably another # instance of the same cell is already there. if not is_loc_free(new_loc, vpr_tile_grid): continue bwd_loc_map[new_loc] = phy_loc if cell.name not in cells_set: cells_set.append(cell.name) tile_type = make_tile_type( [cell], cells_library, tile_types ) vpr_tile_grid[new_loc] = Tile( tile_type.type, name=cell.type, cells=[cell] ) # The tile contains SDIOMUX cell(s). This is an IO tile. if "SDIOMUX" in tile_type.cells: # Split the tile into individual SDIOMUX cells. Each one will be # inside a synthetic tile occupying different grid location. cells = [c for c in tile.cells if c.type == "SDIOMUX"] for i, cell in enumerate(cells): # Create a synthetic tile that will hold just the SDIOMUX cell new_type = make_tile_type([cell], cells_library, tile_types) # Choose a new location for the tile # FIXME: It is assumed that SDIOMUX tiles are on the left edge # of the grid and there is enough space to split them. new_loc = Loc(vpr_loc.x - i, vpr_loc.y, vpr_loc.z) assert new_loc.x >= 1, new_loc # For the offset 0 add the full mapping, for others, just the # backward correspondence. if new_loc == vpr_loc: add_loc_map(phy_loc, new_loc) else: bwd_loc_map[new_loc] = phy_loc # Change index of the cell new_cell = Cell( type=cell.type, index=0, name=cell.name, alias=cell.alias ) # Add the tile instance vpr_tile_grid[new_loc] = Tile( type=new_type.type, name=tile.name, cells=[new_cell] ) # A homogeneous tile if len(tile_type.cells) == 1: cell_type = list(tile_type.cells.keys())[0] # LOGIC, keep as is if cell_type == "LOGIC": add_loc_map(phy_loc, vpr_loc) vpr_tile_grid[vpr_loc] = tile continue # GMUX, split individual GMUX cells into sub-tiles elif cell_type == "GMUX": for i, cell in enumerate(tile.cells): # Create a tile type for a single GMUX cell new_type = make_tile_type( [cell], cells_library, tile_types ) # New location new_loc = Loc(vpr_loc.x, vpr_loc.y, cell.index) # For the offset 0 add the full mapping, for others, just the # backward correspondence. if new_loc == vpr_loc: add_loc_map(phy_loc, new_loc) else: bwd_loc_map[new_loc] = phy_loc # Change index of the cell new_cell = Cell( type=cell.type, index=0, name=cell.name, alias=cell.alias ) # Add the tile instance vpr_tile_grid[new_loc] = Tile( type=new_type.type, name=tile.name, cells=[new_cell] ) continue # Find the ASSP tile. There are multiple tiles that contain the ASSP cell # but in fact there is only one ASSP cell for the whole FPGA which is # "distributed" along top and left edge of the grid. if "ASSP" in tile_types: # Verify that the location is empty assp_loc = Loc(x=1, y=1, z=0) assert is_loc_free(vpr_tile_grid, assp_loc), ("ASSP", assp_loc) # Place the ASSP tile vpr_tile_grid[assp_loc] = Tile( type="ASSP", name="ASSP", cells=[Cell(type="ASSP", index=0, name="ASSP", alias=None)] ) # Remove "FBIO_*" pins from the ASSP tile. These pins are handled by # SDIOMUX IO cells tile_type = tile_types["ASSP"] tile_type.pins = [p for p in tile_type.pins if "FBIO_" not in p.name] # Insert synthetic VCC and GND source tiles. # FIXME: This assumes that the locations specified are empty! for const, loc in [("VCC", Loc(x=2, y=1, z=0)), ("GND", Loc(x=3, y=1, z=0))]: # Verify that the location is empty assert is_loc_free(vpr_tile_grid, loc), (const, loc) # Add the tile instance name = "SYN_{}".format(const) vpr_tile_grid[loc] = Tile( type=name, name=name, cells=[Cell(type=const, index=0, name=const, alias=None)] ) # Extend the grid by 1 on the right and bottom side. Fill missing locs # with empty tiles. for x, y in itertools.product(range(grid_size[0]), range(grid_size[1])): loc = Loc(x=x, y=y, z=0) if loc not in vpr_tile_grid: vpr_tile_grid[loc] = None return vpr_tile_grid, vpr_clock_cells, LocMap( fwd=fwd_loc_map, bwd=bwd_loc_map ),
def main(): # Parse arguments parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument( "--phy-db", type=str, required=True, help="Input physical device database file" ) parser.add_argument( "--sdf-dir", type=str, default=None, help="A directory with SDF timing files" ) parser.add_argument( "--vpr-db", type=str, default="vpr_database.pickle", help="Output VPR database file" ) parser.add_argument( "--grid-limit", type=str, default=None, help="Grid coordinate range to import eg. '0,0,10,10' (def. None)" ) args = parser.parse_args() # Grid limit if args.grid_limit is not None: grid_limit = [int(q) for q in args.grid_limit.split(",")] else: grid_limit = None # Load data from the database with open(args.phy_db, "rb") as fp: db = pickle.load(fp) phy_quadrants = db["phy_quadrants"] cells_library = db["cells_library"] tile_types = db["tile_types"] phy_tile_grid = db["phy_tile_grid"] phy_clock_cells = db["phy_clock_cells"] switchbox_types = db["switchbox_types"] phy_switchbox_grid = db["switchbox_grid"] switchbox_timing = db["switchbox_timing"] connections = db["connections"] package_pinmaps = db["package_pinmaps"] # Load and parse SDF files if args.sdf_dir is not None: cell_timings = load_sdf_timings(args.sdf_dir) else: cell_timings = None # Process the cells library vpr_cells_library = process_cells_library(cells_library) # Add synthetic stuff add_synthetic_cell_and_tile_types(tile_types, vpr_cells_library) # Determine the grid offset so occupied locations start at GRID_MARGIN tl_min = min([loc.x for loc in phy_tile_grid]), min( [loc.y for loc in phy_tile_grid] ) tl_max = max([loc.x for loc in phy_tile_grid]), max( [loc.y for loc in phy_tile_grid] ) sb_min = min([loc.x for loc in phy_switchbox_grid]), min( [loc.y for loc in phy_switchbox_grid] ) sb_max = max([loc.x for loc in phy_switchbox_grid]), max( [loc.y for loc in phy_switchbox_grid] ) grid_min = min(tl_min[0], sb_min[0]), min(tl_min[1], sb_min[1]) grid_max = max(tl_max[0], sb_max[0]), max(tl_max[1], sb_max[1]) # Compute VPR grid offset w.r.t the physical grid and its size grid_offset = GRID_MARGIN[0] - grid_min[0], GRID_MARGIN[1] - grid_min[1] grid_size = GRID_MARGIN[0] + GRID_MARGIN[2] + ( grid_max[0] - grid_min[0] + 1 ), GRID_MARGIN[1] + GRID_MARGIN[3] + ( grid_max[1] - grid_min[1] + 1 ) # Remap quadrant locations vpr_quadrants = {} for quadrant in phy_quadrants.values(): vpr_quadrants[quadrant.name] = Quadrant( name=quadrant.name, x0=quadrant.x0 + grid_offset[0], x1=quadrant.x1 + grid_offset[0], y0=quadrant.y0 + grid_offset[1], y1=quadrant.y1 + grid_offset[1] ) # Process the tilegrid vpr_tile_grid, vpr_clock_cells, loc_map = process_tilegrid( tile_types, phy_tile_grid, phy_clock_cells, vpr_cells_library, grid_size, grid_offset, grid_limit ) # Process the switchbox grid vpr_switchbox_grid, loc_map = process_switchbox_grid( phy_switchbox_grid, loc_map, grid_offset, grid_limit ) # Process connections connections = process_connections( connections, loc_map, vpr_tile_grid, phy_tile_grid, grid_limit ) # Process package pinmaps vpr_package_pinmaps = {} for package, pkg_pin_map in package_pinmaps.items(): vpr_package_pinmaps[package] = process_package_pinmap( pkg_pin_map, vpr_tile_grid, grid_limit ) # Get tile types present in the grid vpr_tile_types = set( [t.type for t in vpr_tile_grid.values() if t is not None] ) vpr_tile_types = { k: v for k, v in tile_types.items() if k in vpr_tile_types } # Get the switchbox types present in the grid vpr_switchbox_types = set( [s for s in vpr_switchbox_grid.values() if s is not None] ) vpr_switchbox_types = { k: v for k, v in switchbox_types.items() if k in vpr_switchbox_types } # Make tile -> site equivalence list vpr_equivalent_sites = {} # Make switch list vpr_switches = build_switch_list() # Make segment list vpr_segments = build_segment_list() # Process timing data if switchbox_timing is not None or cell_timings is not None: print("Processing timing data...") if switchbox_timing is not None: # The timing data seems to be the same for each switchbox type and is # stored under the SB_LC name. timing_data = switchbox_timing["SB_LC"] # Compute the timing model for the most generic SB_LC switchbox. switchbox = vpr_switchbox_types["SB_LC"] driver_timing, sink_map = compute_switchbox_timing_model( switchbox, timing_data ) # Populate the model, create and assign VPR switches. populate_switchbox_timing( switchbox, driver_timing, sink_map, vpr_switches ) # Propagate the timing data to all other switchboxes. Even though they # are of different type, physically they are the same. for dst_switchbox in vpr_switchbox_types.values(): if dst_switchbox.type != "SB_LC": copy_switchbox_timing(switchbox, dst_switchbox) if cell_timings is not None: sw = add_vpr_switches_for_cell("QMUX", cell_timings) vpr_switches.update(sw) sw = add_vpr_switches_for_cell("CAND", cell_timings) vpr_switches.update(sw) if DEBUG: # DBEUG print("Tile grid:") xmax = max([loc.x for loc in vpr_tile_grid]) ymax = max([loc.y for loc in vpr_tile_grid]) for y in range(ymax + 1): line = " {:>2}: ".format(y) for x in range(xmax + 1): loc = Loc(x=x, y=y, z=0) if loc not in vpr_tile_grid: line += " " elif vpr_tile_grid[loc] is not None: tile_type = vpr_tile_types[vpr_tile_grid[loc].type] label = sorted(list(tile_type.cells.keys()))[0][0].upper() line += label else: line += "." print(line) # DBEUG print("Tile capacity / sub-tile count") xmax = max([loc.x for loc in vpr_tile_grid]) ymax = max([loc.y for loc in vpr_tile_grid]) for y in range(ymax + 1): line = " {:>2}: ".format(y) for x in range(xmax + 1): tiles = { loc: tile for loc, tile in vpr_tile_grid.items() if loc.x == x and loc.y == y } count = len([t for t in tiles.values() if t is not None]) if len(tiles) == 0: line += " " elif count == 0: line += "." else: line += "{:X}".format(count) print(line) # DEBUG print("Switchbox grid:") xmax = max([loc.x for loc in vpr_switchbox_grid]) ymax = max([loc.y for loc in vpr_switchbox_grid]) for y in range(ymax + 1): line = " {:>2}: ".format(y) for x in range(xmax + 1): loc = Loc(x=x, y=y, z=0) if loc not in vpr_switchbox_grid: line += " " elif vpr_switchbox_grid[loc] is not None: line += "X" else: line += "." print(line) # DBEUG print("Route-through global clock cells:") xmax = max([loc.x for loc in vpr_tile_grid]) ymax = max([loc.y for loc in vpr_tile_grid]) for y in range(ymax + 1): line = " {:>2}: ".format(y) for x in range(xmax + 1): loc = Loc(x=x, y=y, z=0) for cell in vpr_clock_cells.values(): if cell.loc == loc: line += cell.name[0].upper() break else: line += "." print(line) # DEBUG print("VPR Segments:") for s in vpr_segments.values(): print("", s) # DEBUG print("VPR Switches:") for s in vpr_switches.values(): print("", s) # Prepare the VPR database and write it db_root = { "vpr_cells_library": vpr_cells_library, "loc_map": loc_map, "vpr_quadrants": vpr_quadrants, "vpr_tile_types": vpr_tile_types, "vpr_tile_grid": vpr_tile_grid, "vpr_clock_cells": vpr_clock_cells, "vpr_equivalent_sites": vpr_equivalent_sites, "vpr_switchbox_types": vpr_switchbox_types, "vpr_switchbox_grid": vpr_switchbox_grid, "connections": connections, "vpr_package_pinmaps": vpr_package_pinmaps, "segments": list(vpr_segments.values()), "switches": list(vpr_switches.values()), } with open("{}.tmp".format(args.vpr_db), "wb") as fp: pickle.dump(db_root, fp, protocol=3) os.rename("{}.tmp".format(args.vpr_db), args.vpr_db)
def build_hop_connections(switchbox_types, switchbox_grid): """ Builds HOP connections between switchboxes. """ connections = [] # Determine the switchbox grid limits xs = set([loc.x for loc in switchbox_grid.keys()]) ys = set([loc.y for loc in switchbox_grid.keys()]) loc_min = Loc(min(xs), min(ys), 0) loc_max = Loc(max(xs), max(ys), 0) # Identify all connections that go out of switchboxes for dst_loc, dst_switchbox_type in switchbox_grid.items(): dst_switchbox = switchbox_types[dst_switchbox_type] # Process HOP inputs. No need for looping over outputs as each output # should go into a HOP input. dst_pins = [ pin for pin in dst_switchbox.inputs.values() if pin.type == SwitchboxPinType.HOP ] for dst_pin in dst_pins: # Parse the name, determine hop offset. Skip non-hop wires. hop_name, hop_ofs = get_name_and_hop(dst_pin.name) if hop_ofs is None: continue # Check if we don't hop outside the FPGA grid. src_loc = Loc(dst_loc.x + hop_ofs[0], dst_loc.y + hop_ofs[1], 0) if src_loc.x < loc_min.x or src_loc.x > loc_max.x: continue if src_loc.y < loc_min.y or src_loc.y > loc_max.y: continue # Get the switchbox at the source location if src_loc not in switchbox_grid: print( "WARNING: No switchbox at '{}' for input '{}' of switchbox '{}' at '{}'" .format(src_loc, dst_pin.name, dst_switchbox_type, dst_loc)) continue src_switchbox_type = switchbox_grid[src_loc] src_switchbox = switchbox_types[src_switchbox_type] # Check if there is a matching input pin in that switchbox src_pins = [ pin for pin in src_switchbox.outputs.values() if pin.name == hop_name ] if len(src_pins) != 1: print( "WARNING: No output pin '{}' in switchbox '{}'" " at '{}' for input '{}' of switchbox '{}' at '{}'".format( hop_name, src_switchbox_type, src_loc, dst_pin.name, dst_switchbox_type, dst_loc)) continue src_pin = src_pins[0] # Add the connection connection = Connection(src=ConnectionLoc( loc=src_loc, pin=src_pin.name, type=ConnectionType.SWITCHBOX, ), dst=ConnectionLoc( loc=dst_loc, pin=dst_pin.name, type=ConnectionType.SWITCHBOX, ), is_direct=False) connections.append(connection) return connections
def build_tile_connections(tile_types, tile_grid, switchbox_types, switchbox_grid): """ Build local and foreign connections between all switchboxes and tiles. """ connections = [] # Process switchboxes for loc, switchbox_type in switchbox_grid.items(): switchbox = switchbox_types[switchbox_type] # Get pins sbox_pins = [ pin for pin in switchbox.pins if pin.type in [SwitchboxPinType.LOCAL, SwitchboxPinType.FOREIGN] ] for sbox_pin in sbox_pins: tile = None # A local connection if sbox_pin.type == SwitchboxPinType.LOCAL: pin_name = sbox_pin.name tile_loc = loc # A foreign connection elif sbox_pin.type == SwitchboxPinType.FOREIGN: # Get the hop offset pin_name, hop = get_name_and_hop(sbox_pin.name) assert hop is not None, sbox_pin tile_loc = Loc(x=loc.x + hop[0], y=loc.y + hop[1], z=loc.z) # Get the tile if tile_loc not in tile_grid: print("WARNING: No tile at loc '{}' for pin '{}'".format( tile_loc, sbox_pin.name)) continue tile = tile_types[tile_grid[tile_loc].type] # Find the pin in the tile for pin in tile.pins: if pin.direction == OPPOSITE_DIRECTION[sbox_pin.direction]: # Check if the pin name refers to the full tile pin name # ie. with the cell name. if pin.name == pin_name: tile_pin = pin break # Split the pin name into cell name + pin name, check only # if the latter matches. cell, name = pin.name.split("_", maxsplit=1) if name == pin_name: tile_pin = pin break else: tile_pin = None # Pin not found if tile_pin is None: print( "WARNING: No pin in tile at '{}' found for switchbox pin '{}' of '{}' at '{}'" .format(tile_loc, sbox_pin.name, switchbox.type, loc)) continue # Add the connection src = ConnectionLoc( loc=loc, pin=sbox_pin.name, type=ConnectionType.SWITCHBOX, ) dst = ConnectionLoc( loc=tile_loc, pin=tile_pin.name, type=ConnectionType.TILE, ) if sbox_pin.direction == PinDirection.OUTPUT: connection = Connection(src=src, dst=dst, is_direct=False) if sbox_pin.direction == PinDirection.INPUT: connection = Connection(src=dst, dst=src, is_direct=False) connections.append(connection) return connections
def add_tracks_for_const_network(graph, const, tile_grid): """ Builds a network of CHANX/CHANY and edges to propagate signal from a const source. The const network is purely artificial and does not correspond to any physical routing resources. Returns a map of const network nodes for each location. """ # Get the tilegrid span xs = set([loc.x for loc in tile_grid]) ys = set([loc.y for loc in tile_grid]) xmin, ymin = min(xs), min(ys) xmax, ymax = max(xs), max(ys) # Get segment id and switch id segment_id = graph.get_segment_id_from_name(const.lower()) switch_id = graph.get_delayless_switch_id() # Find the source tile src_loc = [ loc for loc, t in tile_grid.items() if t is not None and t.type == "SYN_{}".format(const) ] assert len(src_loc) == 1, const src_loc = src_loc[0] # Go down from the source to the edge of the tilegrid entry_node, col_node, _ = add_track_chain(graph, "Y", src_loc.x, src_loc.y, 1, segment_id, switch_id) # Connect the tile OPIN to the column pin_name = "TL-SYN_{const}.{const}0_{const}[0]".format(const=const) opin_node = graph.get_nodes_for_pin((src_loc[0], src_loc[1]), pin_name) assert len(opin_node) == 1, pin_name add_edge(graph, opin_node[0][0], entry_node.id, switch_id) # Got left and right from the source column over the bottommost row row_entry_node1, _, row_node_map1 = add_track_chain( graph, "X", 0, src_loc.x, 1, segment_id, switch_id) row_entry_node2, _, row_node_map2 = add_track_chain( graph, "X", 0, src_loc.x + 1, xmax - 1, segment_id, switch_id) # Connect rows to the column add_edge(graph, col_node.id, row_entry_node1.id, switch_id) add_edge(graph, col_node.id, row_entry_node2.id, switch_id) row_node_map = {**row_node_map1, **row_node_map2} row_node_map[0] = row_node_map[1] # For each column add one that spand over the entire grid height const_node_map = {} for x in range(xmin, xmax): # Add the column col_entry_node, _, col_node_map = add_track_chain( graph, "Y", x, ymin + 1, ymax - 1, segment_id, switch_id) # Add edge fom the horizontal row add_edge(graph, row_node_map[x].id, col_entry_node.id, switch_id) # Populate the const node map for y, node in col_node_map.items(): const_node_map[Loc(x=x, y=y, z=0)] = node return const_node_map
def __init__(self, vpr_db, package_name): '''Prepares required structures for converting FASM to BELs. Parameters ---------- vpr_db: dict A dictionary containing cell_library, loc_map, vpr_tile_types, vpr_tile_grid, vpr_switchbox_types, vpr_switchbox_grid, connections, vpr_package_pinmaps ''' # load vpr_db data self.quadrants = vpr_db["phy_quadrants"] self.cells_library = vpr_db["cells_library"] self.vpr_tile_types = vpr_db["tile_types"] self.vpr_tile_grid = vpr_db["phy_tile_grid"] self.vpr_switchbox_types = vpr_db["switchbox_types"] self.vpr_switchbox_grid = vpr_db["switchbox_grid"] self.connections = vpr_db["connections"] self.package_name = package_name self.io_to_fbio = dict() for name, package in db['package_pinmaps'][self.package_name].items(): self.io_to_fbio[package[0].loc] = name # Add ASSP to all locations it covers # TODO maybe this should be added in original vpr_tile_grid # set all cels in row 1 and column 2 to ASSP # In VPR grid, the ASSP tile is located in (1, 1) assplocs = set() ramlocs = dict() multlocs = dict() for phy_loc, tile in self.vpr_tile_grid.items(): tile_type = self.vpr_tile_types[tile.type] if "ASSP" in tile_type.cells: assplocs.add(phy_loc) if "RAM" in tile_type.cells: ramcell = [cell for cell in tile.cells if cell.type == "RAM"] cellname = ramcell[0].name if cellname not in ramlocs: ramlocs[cellname] = set() ramlocs[cellname].add(phy_loc) if "MULT" in tile_type.cells: multcell = [cell for cell in tile.cells if cell.type == "MULT"] cellname = multcell[0].name if cellname not in multlocs: multlocs[cellname] = set() multlocs[cellname].add(phy_loc) # this map represents the mapping from input name to its inverter name self.inversionpins = { 'LOGIC': { 'TA1': 'TAS1', 'TA2': 'TAS2', 'TB1': 'TBS1', 'TB2': 'TBS2', 'BA1': 'BAS1', 'BA2': 'BAS2', 'BB1': 'BBS1', 'BB2': 'BBS2', 'QCK': 'QCKS' } } # prepare helper structure for connections self.connections_by_loc = defaultdict(list) for connection in self.connections: self.connections_by_loc[connection.dst].append(connection) self.connections_by_loc[connection.src].append(connection) # a mapping from the type of cell FASM line refers to to its parser self.featureparsers = { 'LOGIC': self.parse_logic_line, 'QMUX': self.parse_logic_line, 'GMUX': self.parse_logic_line, 'INTERFACE': self.parse_interface_line, 'ROUTING': self.parse_routing_line, 'CAND0': self.parse_colclk_line, 'CAND1': self.parse_colclk_line, 'CAND2': self.parse_colclk_line, 'CAND3': self.parse_colclk_line, 'CAND4': self.parse_colclk_line, } # a mapping from cell type to a set of possible pin names self.pinnames = defaultdict(set) for celltype in self.cells_library.values(): typ = celltype.type for pin in celltype.pins: self.pinnames[typ].add(pin.name) # a mapping from cell types that occupy multiple locations # to a single location self.multiloccells = { 'ASSP': MultiLocCellMapping('ASSP', assplocs, Loc(1, 1, 0), self.pinnames['ASSP']) } for ram in ramlocs: self.multiloccells[ram] = MultiLocCellMapping( ram, ramlocs[ram], list(ramlocs[ram])[0], self.pinnames['RAM']) for mult in multlocs: self.multiloccells[mult] = MultiLocCellMapping( mult, multlocs[mult], list(multlocs[mult])[1], self.pinnames['MULT']) # helper routing data self.routingdata = defaultdict(list) # a dictionary holding bit settings for BELs self.belinversions = defaultdict(lambda: defaultdict(list)) # a dictionary holding bit settings for IOs self.interfaces = defaultdict(lambda: defaultdict(list)) # a dictionary holding simplified connections between BELs self.designconnections = defaultdict(dict) # a dictionary holding hops from routing self.designhops = defaultdict(dict) # Clock column drivers (CAND) data self.colclk_data = defaultdict(lambda: defaultdict(list)) # A map of clock wires that connect to switchboxes self.cand_map = defaultdict(lambda: dict()) # A map of original (loc, pin) to new (loc, pin). Created during # aggregation of multi-loc cells. self.org_loc_map = {}
def parse_port_mapping_table(xml_root, switchbox_grid): """ Parses switchbox port mapping tables. Returns a dict indexed by locations containing a dict with switchbox port to tile port name correspondence. """ port_maps = defaultdict(lambda: {}) # Sections are named "*_Table" xml_tables = [e for e in xml_root if e.tag.endswith("_Table")] for xml_table in xml_tables: # Get the origin origin = xml_table.tag.split("_")[0] assert origin in ["Left", "Right", "Top", "Bottom"], origin # Get switchbox types affected by the mapping sbox_types_xml = xml_table.find("SBoxTypes") assert sbox_types_xml is not None switchbox_types = set([ v for k, v in sbox_types_xml.attrib.items() if k.startswith("type") ]) # Get their locations locs = [ loc for loc, type in switchbox_grid.items() if type in switchbox_types ] # Get the first occurrence of a switchbox with one of considered types # that is closes to the (0, 0) according to manhattan distance. base_loc = None for loc in locs: if not base_loc: base_loc = loc elif (loc.x + loc.y) < (base_loc.x + base_loc.y): base_loc = loc # Parse the port mapping table(s) for port_mapping_xml in xml_table.findall("PortMappingTable"): # Get the direction of the switchbox offset orientation = port_mapping_xml.attrib["Orientation"] if orientation == "Horizontal": assert origin in ["Top", "Bottom"], (origin, orientation) dx, dy = (+1, 0) elif orientation == "Vertical": assert origin in ["Left", "Right"], (origin, orientation) dx, dy = (0, +1) # Process the mapping of switchbox output ports for index_xml in port_mapping_xml.findall("Index"): pin_name = index_xml.attrib["Mapped_Interface_Name"] output_num = index_xml.attrib["SwitchOutputNum"] # Determine the mapped port direction if output_num == "-1": pin_direction = PinDirection.INPUT else: pin_direction = PinDirection.OUTPUT sbox_xmls = [e for e in index_xml if e.tag.startswith("SBox")] for sbox_xml in sbox_xmls: offset = int(sbox_xml.attrib["Offset"]) mapped_name = sbox_xml.get("MTB_PortName", None) # "-1" means unconnected if mapped_name == "-1": mapped_name = None # Get the location for the map loc = Loc(x=base_loc.x + dx * offset, y=base_loc.y + dy * offset, z=0) # Append mapping key = (pin_name, pin_direction) assert key not in port_maps[loc], (loc, key) port_maps[loc][key] = mapped_name # Make a normal dict port_maps = dict(port_maps) return port_maps
def write_tilegrid(xml_arch, arch_tile_grid, loc_map, layout_name): """ Generates the "layout" section of the arch XML and appends it to the root given. """ # Remove the "layout" tag if any xml_layout = xml_arch.find("layout") if xml_layout is not None: xml_arch.remove(xml_layout) # Grid size xs = [flat_loc[0] for flat_loc in arch_tile_grid] ys = [flat_loc[1] for flat_loc in arch_tile_grid] w = max(xs) + 1 h = max(ys) + 1 # Fixed layout xml_layout = ET.SubElement(xml_arch, "layout") xml_fixed = ET.SubElement(xml_layout, "fixed_layout", { "name": layout_name, "width": str(w), "height": str(h), }) # Individual tiles for flat_loc, tile in arch_tile_grid.items(): if tile is None: continue # Unpack tile_type, capacity = tile # Single tile xml_sing = ET.SubElement( xml_fixed, "single", { "type": "TL-{}".format(tile_type.upper()), "x": str(flat_loc[0]), "y": str(flat_loc[1]), "priority": str(10), # Not sure if we need this }) # Gather metadata metadata = [] for i in range(capacity): loc = Loc(x=flat_loc[0], y=flat_loc[1], z=i) if loc in loc_map.bwd: phy_loc = loc_map.bwd[loc] metadata.append("X{}Y{}".format(phy_loc.x, phy_loc.y)) # Emit metadata if any if len(metadata): xml_metadata = ET.SubElement(xml_sing, "metadata") xml_meta = ET.SubElement(xml_metadata, "meta", { "name": "fasm_prefix", }) xml_meta.text = " ".join(metadata)
def write_direct_connections(xml_arch, tile_grid, connections): """ """ def get_tile(ep): """ Retireves tile for the given connection endpoint """ if ep.loc in tile_grid and tile_grid[ep.loc] is not None: return tile_grid[ep.loc] else: print("ERROR: No tile found for the connection endpoint", ep) return None # Remove the "directlist" tag if any xml_directlist = xml_arch.find("directlist") if xml_directlist is not None: xml_arch.remove(xml_directlist) # Make a new one xml_directlist = ET.SubElement(xml_arch, "directlist") # Populate connections conns = [c for c in connections if is_direct(c)] for connection in conns: src_tile = get_tile(connection.src) dst_tile = get_tile(connection.dst) if not src_tile or not dst_tile: continue src_name = "TL-{}.{}".format(src_tile.type, connection.src.pin) dst_name = "TL-{}.{}".format(dst_tile.type, connection.dst.pin) name = "{}_at_X{}Y{}Z{}_to_{}_at_X{}Y{}Z{}".format( src_name, connection.src.loc.x, connection.src.loc.y, connection.src.loc.z, dst_name, connection.dst.loc.x, connection.dst.loc.y, connection.dst.loc.z, ) delta_loc = Loc( x=connection.dst.loc.x - connection.src.loc.x, y=connection.dst.loc.y - connection.src.loc.y, z=connection.dst.loc.z - connection.src.loc.z, ) # Format the direct connection tag ET.SubElement( xml_directlist, "direct", { "name": name, "from_pin": src_name, "to_pin": dst_name, "x_offset": str(delta_loc.x), "y_offset": str(delta_loc.y), "z_offset": str(delta_loc.z), })
def process_connections( phy_connections, loc_map, vpr_tile_grid, phy_tile_grid, grid_limit=None ): """ Process the connection list. """ # Remap locations, create the VPR connection list vpr_connections = [] for connection in phy_connections: # Reject connections that reach outsite the grid limit if not is_loc_within_limit(connection.src.loc, grid_limit): continue if not is_loc_within_limit(connection.dst.loc, grid_limit): continue # Remap source and destination coordinates eps = [connection.src, connection.dst] for j, ep in enumerate(eps): phy_loc = ep.loc vpr_loc = loc_map.fwd[phy_loc] vpr_pin = ep.pin # If the connection mentions a CAND cell, fixup its location if "CAND" in ep.pin and ep.type == ConnectionType.CLOCK: vpr_loc = fixup_cand_loc(vpr_loc, phy_loc) # If the connection mentions a GMUX cell, remap its Z location so # it points to the correct sub-tile if "GMUX" in ep.pin and ep.type == ConnectionType.TILE: # Modify the cell name, use always "GMUX0" cell, pin = ep.pin.split("_", maxsplit=1) vpr_pin = "GMUX0_{}".format(pin) # Modify the location according to the cell index z = int(cell[-1]) # FIXME: Assuming indices 0-9 vpr_loc = Loc(vpr_loc.x, vpr_loc.y, z) # Update the endpoint eps[j] = ConnectionLoc(loc=vpr_loc, pin=vpr_pin, type=ep.type) # Add the connection vpr_connections.append( Connection(src=eps[0], dst=eps[1], is_direct=connection.is_direct) ) # Remap locations of connections that go to CLOCK pads. A physical # BIDIR+CLOCK tile is split into separate BIDIR and CLOCK tiles. for i, connection in enumerate(vpr_connections): eps = [connection.src, connection.dst] for j, ep in enumerate(eps): # This endpoint is not relevant to a CLOCK cell if not ep.pin.startswith("CLOCK"): continue # The endpoint location points to a BIDIR tile. Find the assocated # CLOCK tile org_loc = loc_map.bwd[ep.loc] for vpr_loc, phy_loc in loc_map.bwd.items(): if phy_loc == org_loc and vpr_loc != ep.loc: clock_loc = vpr_loc break else: assert False, ( "Couldn't find a CLOCK cell in the VPR grid!", connection ) eps[j] = ConnectionLoc( loc=clock_loc, pin=ep.pin, type=ep.type, ) # Modify the connection vpr_connections[i] = Connection( src=eps[0], dst=eps[1], is_direct=connection.is_direct ) # Find SFBIO connections, map their endpoints to SDIOMUX tiles # FIXME: This should be read from the techfine. Definition of the SDIOMUX # cell has "realPortName" fields. SDIOMUX_PIN_MAP = { "FBIO_In": "IZ", "FBIO_In_En": "IE", "FBIO_Out": "OQI", "FBIO_Out_En": "OE", } for i, connection in enumerate(vpr_connections): eps = [connection.src, connection.dst] for j, ep in enumerate(eps): # Must have "ASSP" and "FBIO_" in name and refer to a tile. if "ASSP" not in ep.pin: continue if "FBIO_" not in ep.pin: continue if ep.type != ConnectionType.TILE: continue # Get the pin name and index pin_name, pin_index = get_pin_name(ep.pin) assert pin_index is not None, ep # Strip cell name pin_name = pin_name.split("_", maxsplit=1)[1] # Find where is an SDIOMUX cell for that index cell_name = "SFB_{}_IO".format(pin_index) # New location and pin name new_loc = get_loc_of_cell(cell_name, vpr_tile_grid) cell = find_cell_in_tile(cell_name, vpr_tile_grid[new_loc]) new_pin = "{}{}_{}".format( cell.type, cell.index, SDIOMUX_PIN_MAP[pin_name] ) eps[j] = ConnectionLoc( loc=new_loc, pin=new_pin, type=ep.type, ) # Modify the connection vpr_connections[i] = Connection( src=eps[0], dst=eps[1], is_direct=connection.is_direct ) # Find locations of "special" tiles special_tile_loc = {"ASSP": None} for loc, tile in vpr_tile_grid.items(): if tile is not None and tile.type in special_tile_loc: assert special_tile_loc[tile.type] is None, tile special_tile_loc[tile.type] = loc # Map connections going to/from them to their locations in the VPR grid for i, connection in enumerate(vpr_connections): # Process connection endpoints eps = [connection.src, connection.dst] for j, ep in enumerate(eps): if ep.type != ConnectionType.TILE: continue cell_name, pin = ep.pin.split("_", maxsplit=1) cell_type = cell_name[:-1] # FIXME: The above will fail on cell with index >= 10 if cell_type in special_tile_loc: loc = special_tile_loc[cell_type] eps[j] = ConnectionLoc( loc=loc, pin=ep.pin, type=ep.type, ) # Modify the connection vpr_connections[i] = Connection( src=eps[0], dst=eps[1], is_direct=connection.is_direct ) # handle RAM and MULT locations ram_locations = {} mult_locations = {} for loc, tile in vpr_tile_grid.items(): if tile is None: continue cell = tile.cells[0] cell_name = cell.name if tile.type == "RAM": ram_locations[cell_name] = loc if tile.type == "MULT": mult_locations[cell_name] = loc for i, connection in enumerate(vpr_connections): # Process connection endpoints eps = [connection.src, connection.dst] for j, ep in enumerate(eps): if ep.type != ConnectionType.TILE: continue cell_name, pin = ep.pin.split("_", maxsplit=1) cell_type = cell_name[:-1] # FIXME: The above will fail on cell with index >= 10 # We handle on MULT and RAM here if cell_type != "MULT" and cell_type != "RAM": continue loc = loc_map.bwd[ep.loc] tile = phy_tile_grid[loc] cell = [cell for cell in tile.cells if cell.type == cell_type] cell_name = cell[0].name if cell_type == "MULT": loc = mult_locations[cell_name] else: loc = ram_locations[cell_name] eps[j] = ConnectionLoc( loc=loc, pin=ep.pin, type=ep.type, ) # Modify the connection vpr_connections[i] = Connection( src=eps[0], dst=eps[1], is_direct=connection.is_direct ) # A QMUX should have 3 QCLKIN inputs but accorting to the EOS S3/PP3E # techfile it has only one. It is assumed then when "QCLKIN0=GMUX_1" then # "QCLKIN1=GMUX_2" etc. new_qmux_connections = [] for connection in vpr_connections: # Get only those that target QCLKIN0 of a QMUX. if connection.dst.type != ConnectionType.CLOCK: continue if connection.src.type != ConnectionType.TILE: continue dst_cell_name, dst_pin = connection.dst.pin.split(".", maxsplit=1) if not dst_cell_name.startswith("QMUX") or dst_pin != "QCLKIN0": continue src_cell_name, src_pin = connection.src.pin.split("_", maxsplit=1) if not src_cell_name.startswith("GMUX"): continue # Add two new connections for QCLKIN1 and QCLKIN2. # GMUX connections are already spread along the Z axis so the Z # coordinate indicates the GMUX cell index. gmux_base = connection.src.loc.z for i in [1, 2]: gmux_idx = (gmux_base + i) % 5 c = Connection( src=ConnectionLoc( loc=Loc( x=connection.src.loc.x, y=connection.src.loc.y, z=gmux_idx ), pin="GMUX0_IZ", type=connection.src.type ), dst=ConnectionLoc( loc=connection.dst.loc, pin="{}.QCLKIN{}".format(dst_cell_name, i), type=connection.dst.type ), is_direct=connection.is_direct ) new_qmux_connections.append(c) vpr_connections.extend(new_qmux_connections) # Handle QMUX connections. Instead of making them SWITCHBOX -> TILE convert # to SWITCHBOX -> CLOCK for i, connection in enumerate(vpr_connections): # Process connection endpoints eps = [connection.src, connection.dst] for j, ep in enumerate(eps): if ep.type != ConnectionType.TILE: continue cell_name, pin = ep.pin.split("_", maxsplit=1) cell_index = int(cell_name[-1]) cell_type = cell_name[:-1] # FIXME: The above will fail on cell with index >= 10 # Only QMUX if cell_type != "QMUX": continue # Get the physical tile loc = loc_map.bwd[ep.loc] tile = phy_tile_grid[loc] # Find the cell in the tile cells = [ c for c in tile.cells if c.type == "QMUX" and c.index == cell_index ] assert len(cells) == 1 cell = cells[0] # Modify the endpoint eps[j] = ConnectionLoc( loc=ep.loc, pin="{}.{}".format(cell.name, pin), type=ConnectionType.CLOCK, ) # Modify the connection vpr_connections[i] = Connection( src=eps[0], dst=eps[1], is_direct=connection.is_direct ) return vpr_connections
def parse_wire_mapping_table(xml_root, switchbox_grid, switchbox_types): """ Parses the "DeviceWireMappingTable" section. Returns a dict indexed by locations. """ def yield_locs_and_maps(): """ Yields locations and wire mappings associated with it. """ RE_LOC = re.compile(r"^(Row|Col)_([0-9]+)_([0-9]+)$") # Rows xml_rows = [e for e in xml_root if e.tag.startswith("Row_")] for xml_row in xml_rows: # Decode row range match = RE_LOC.match(xml_row.tag) assert match is not None, xml_row.tag row_beg = int(xml_row.attrib["RowStartNum"]) row_end = int(xml_row.attrib["RowEndNum"]) assert row_beg == int(match.group(2)), \ (xml_row.tag, row_beg, row_end) assert row_end == int(match.group(3)), \ (xml_row.tag, row_beg, row_end) # Columns xml_cols = [e for e in xml_row if e.tag.startswith("Col_")] for xml_col in xml_cols: # Decode column range match = RE_LOC.match(xml_col.tag) assert match is not None, xml_col.tag col_beg = int(xml_col.attrib["ColStartNum"]) col_end = int(xml_col.attrib["ColEndNum"]) assert col_beg == int(match.group(2)), \ (xml_col.tag, col_beg, col_end) assert col_end == int(match.group(3)), \ (xml_col.tag, col_beg, col_end) # Wire maps xml_maps = [e for e in xml_col if e.tag.startswith("Stage_")] # Yield wire maps for each location for y in range(row_beg, row_end + 1): for x in range(col_beg, col_end + 1): yield (Loc(x=x, y=y, z=0), xml_maps) # Process wire maps wire_maps = defaultdict(lambda: {}) RE_STAGE = re.compile(r"^Stage_([0-9])$") RE_JOINT = re.compile(r"^Join\.([0-9]+)\.([0-9]+)\.([0-9]+)$") RE_WIREMAP = re.compile( r"^WireMap\.(Top|Bottom|Left|Right)\.Length_([0-9])\.(.*)$") for loc, xml_maps in yield_locs_and_maps(): for xml_map in xml_maps: # Decode stage id match = RE_STAGE.match(xml_map.tag) assert match is not None, xml_map.tag stage_id = int(xml_map.attrib["StageNumber"]) assert stage_id == int(match.group(1)), \ (xml_map.tag, stage_id) # Decode wire joints joints = { k: v for k, v in xml_map.attrib.items() if k.startswith("Join.") } for joint_key, joint_map in joints.items(): # Decode the joint key match = RE_JOINT.match(joint_key) assert match is not None, joint_key pin_loc = SwitchboxPinLoc( stage_id=stage_id, switch_id=int(match.group(1)), mux_id=int(match.group(2)), pin_id=int(match.group(3)), pin_direction=PinDirection. INPUT # FIXME: Are those always inputs ? ) # Decode the wire name match = RE_WIREMAP.match(joint_map) assert match is not None, joint_map wire_hop_dir = match.group(1) wire_hop_len = int(match.group(2)) wire_name = match.group(3) # Compute location of the tile that the wire is connected to if wire_hop_dir == "Top": tile_loc = Loc(x=loc.x, y=loc.y - wire_hop_len, z=0) elif wire_hop_dir == "Bottom": tile_loc = Loc(x=loc.x, y=loc.y + wire_hop_len, z=0) elif wire_hop_dir == "Left": tile_loc = Loc(x=loc.x - wire_hop_len, y=loc.y, z=0) elif wire_hop_dir == "Right": tile_loc = Loc(x=loc.x + wire_hop_len, y=loc.y, z=0) else: assert False, wire_hop_dir # Append to the map wire_maps[loc][pin_loc] = (wire_name, tile_loc) return wire_maps