| #!/usr/bin/env python3 | |
| import argparse | |
| import pickle | |
| import itertools | |
| import os | |
| from collections import defaultdict | |
| from sdf_timing import sdfparse | |
| from sdf_timing.utils import get_scale_seconds | |
| from data_structs import Pin, PinDirection, Cell, CellType, ClockCell, Loc, LocMap, \ | |
| Tile, TileType, Connection, ConnectionLoc, ConnectionType, PackagePin, VprSwitch, \ | |
| VprSegment, Quadrant | |
| from utils import get_loc_of_cell, find_cell_in_tile | |
| from utils import get_pin_name | |
| from timing import compute_switchbox_timing_model | |
| from timing import populate_switchbox_timing, copy_switchbox_timing | |
| from timing import add_vpr_switches_for_cell | |
| # ============================================================================= | |
| # Grid margin size (left, top, right, bottom) | |
| GRID_MARGIN = (3, 2, 2, 2) | |
| # IO cell types to ignore. They do not correspond to routable IO pads. | |
| IGNORED_IO_CELL_TYPES = ( | |
| "VCC", | |
| "GND", | |
| ) | |
| # ============================================================================= | |
| DEBUG = False | |
| def is_loc_within_limit(loc, limit): | |
| """ | |
| Returns true when the given location lies within the given limit. | |
| Returns true if the limit is None. Coordinates in the limit are | |
| inclusive. | |
| """ | |
| if limit is None: | |
| return True | |
| if loc.x < limit[0] or loc.x > limit[2]: | |
| return False | |
| if loc.y < limit[1] or loc.y > limit[3]: | |
| return False | |
| return True | |
| def is_loc_free(loc, tile_grid): | |
| """ | |
| Checks whether a location in the given tilegrid is free. | |
| """ | |
| if loc not in tile_grid: | |
| return True | |
| if tile_grid[loc] is None: | |
| return True | |
| return False | |
| # ============================================================================= | |
| def process_cells_library(cells_library): | |
| """ | |
| Processes the cells library, modifies some of them according to | |
| requirements of their VPR representation | |
| """ | |
| vpr_cells_library = {} | |
| for cell_type, cell in cells_library.items(): | |
| # If the cell is a QMUX add the missing QCLKIN1 and QCLKIN2 | |
| # input pins. | |
| if cell_type == "QMUX": | |
| cell_pins = cell.pins | |
| for i in [1, 2]: | |
| cell_pins.append( | |
| Pin( | |
| name="QCLKIN{}".format(i), | |
| direction=PinDirection.INPUT, | |
| attrib={"hardWired": "true"} | |
| ) | |
| ) | |
| # Substitute the cell | |
| vpr_cells_library[cell_type] = CellType( | |
| type=cell_type, pins=cell_pins | |
| ) | |
| # Copy the cell | |
| vpr_cells_library[cell_type] = cell | |
| return vpr_cells_library | |
| # ============================================================================= | |
| 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 add_synthetic_cell_and_tile_types(tile_types, cells_library): | |
| # Add a synthetic tile types for the VCC and GND const sources. | |
| # Models of the VCC and GND cells are already there in the cells_library. | |
| for const in ["VCC", "GND"]: | |
| tile_type = TileType("SYN_{}".format(const), {const: 1}) | |
| tile_type.make_pins(cells_library) | |
| tile_types[tile_type.type] = tile_type | |
| def make_tile_type(cells, cells_library, tile_types, fake_const_pin=False): | |
| """ | |
| Creates a tile type given a list of cells that constitute to it. | |
| """ | |
| # Count cell types | |
| cell_types = sorted([c.type for c in cells]) | |
| cell_counts = {t: 0 for t in cell_types} | |
| for cell in cells: | |
| cell_counts[cell.type] += 1 | |
| # Format type name | |
| parts = [] | |
| for t, c in cell_counts.items(): | |
| if c == 1: | |
| parts.append(t) | |
| else: | |
| parts.append("{}x{}".format(c, t)) | |
| type_name = "_".join(parts).upper() | |
| # If the new tile type already exists, use the existing one | |
| if type_name in tile_types: | |
| return tile_types[type_name] | |
| # Create the new tile type | |
| tile_type = TileType( | |
| type=type_name, cells=cell_counts, fake_const_pin=fake_const_pin | |
| ) | |
| # Create pins | |
| tile_type.make_pins(cells_library) | |
| # Add it to the list | |
| tile_types[type_name] = tile_type | |
| return tile_type | |
| def strip_cells(tile, cell_types, tile_types, cells_library): | |
| """ | |
| Removes cells of the particular type from the tile and tile_type. | |
| Possibly creates a new tile type | |
| """ | |
| tile_type = tile_types[tile.type] | |
| # Check if there is something to remove | |
| if not len(set(cell_types) & set(tile_type.cells.keys())): | |
| return tile | |
| # Filter cells | |
| new_cells = [c for c in tile.cells if c.type not in cell_types] | |
| if not new_cells: | |
| return None | |
| # Create the new tile type and tile | |
| new_tile_type = make_tile_type( | |
| new_cells, cells_library, tile_types, tile_type.fake_const_pin | |
| ) | |
| new_tile = Tile(type=new_tile_type.type, name=tile.name, cells=new_cells) | |
| return new_tile | |
| 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 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 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 process_package_pinmap(package_pinmap, vpr_tile_grid, grid_limit=None): | |
| """ | |
| Processes the package pinmap. Reloacted pin mappings. Reject mappings | |
| that lie outside the grid limit. | |
| """ | |
| # Remap locations, reject cells that are either ignored or not in the | |
| # tilegrid. | |
| new_package_pinmap = defaultdict(lambda: []) | |
| for pin_name, pins in package_pinmap.items(): | |
| for pin in pins: | |
| # The loc is outside the grid limit, skip it. | |
| if not is_loc_within_limit(pin.loc, grid_limit): | |
| continue | |
| # Ignore this one | |
| if pin.cell.type in IGNORED_IO_CELL_TYPES: | |
| continue | |
| # Find the cell | |
| loc = get_loc_of_cell(pin.cell.name, vpr_tile_grid) | |
| if loc is None: | |
| continue | |
| cell = find_cell_in_tile(pin.cell.name, vpr_tile_grid[loc]) | |
| assert cell is not None, (loc, pin) | |
| # Remap location | |
| new_package_pinmap[pin.name].append( | |
| PackagePin(name=pin.name, alias=pin.alias, loc=loc, cell=cell) | |
| ) | |
| # Convert to regular dict | |
| new_package_pinmap = dict(**new_package_pinmap) | |
| return new_package_pinmap | |
| # ============================================================================= | |
| def build_switch_list(): | |
| """ | |
| Builds a list of all switch types used by the architecture | |
| """ | |
| switches = {} | |
| # Add a generic mux switch to make VPR happy | |
| switch = VprSwitch( | |
| name="generic", | |
| type="mux", | |
| t_del=1e-15, # A deliberate dummy small delay | |
| r=0.0, | |
| c_in=0.0, | |
| c_out=0.0, | |
| c_int=0.0, | |
| ) | |
| switches[switch.name] = switch | |
| # A delayless short | |
| switch = VprSwitch( | |
| name="short", | |
| type="short", | |
| t_del=0.0, | |
| r=0.0, | |
| c_in=0.0, | |
| c_out=0.0, | |
| c_int=0.0, | |
| ) | |
| switches[switch.name] = switch | |
| return switches | |
| def build_segment_list(): | |
| """ | |
| Builds a list of all segment types used by the architecture | |
| """ | |
| segments = {} | |
| # A generic segment | |
| segment = VprSegment( | |
| name="generic", | |
| length=1, | |
| r_metal=0.0, | |
| c_metal=0.0, | |
| ) | |
| segments[segment.name] = segment | |
| # Padding segment | |
| segment = VprSegment( | |
| name="pad", | |
| length=1, | |
| r_metal=0.0, | |
| c_metal=0.0, | |
| ) | |
| segments[segment.name] = segment | |
| # Switchbox segment | |
| segment = VprSegment( | |
| name="sbox", | |
| length=1, | |
| r_metal=0.0, | |
| c_metal=0.0, | |
| ) | |
| segments[segment.name] = segment | |
| # VCC and GND segments | |
| for const in ["VCC", "GND"]: | |
| segment = VprSegment( | |
| name=const.lower(), | |
| length=1, | |
| r_metal=0.0, | |
| c_metal=0.0, | |
| ) | |
| segments[segment.name] = segment | |
| # HOP wire segments | |
| for i in [1, 2, 3, 4]: | |
| segment = VprSegment( | |
| name="hop{}".format(i), | |
| length=i, | |
| r_metal=0.0, | |
| c_metal=0.0, | |
| ) | |
| segments[segment.name] = segment | |
| # Global clock network segment | |
| segment = VprSegment( | |
| name="clock", | |
| length=1, | |
| r_metal=0.0, | |
| c_metal=0.0, | |
| ) | |
| segments[segment.name] = segment | |
| # A segment for "hop" connections to "special" tiles. | |
| segment = VprSegment( | |
| name="special", | |
| length=1, | |
| r_metal=0.0, | |
| c_metal=0.0, | |
| ) | |
| segments[segment.name] = segment | |
| return segments | |
| # ============================================================================= | |
| def load_sdf_timings(sdf_dir): | |
| """ | |
| Loads and merges SDF timing data from all *.sdf files in the given | |
| directory. | |
| """ | |
| def apply_scale(cells, scale=1.0): | |
| """ | |
| Scales all timings represented by the given SDF structure. | |
| """ | |
| for cell_type, cell_data in cells.items(): | |
| for instance, instance_data in cell_data.items(): | |
| for timing, timing_data in instance_data.items(): | |
| paths = timing_data["delay_paths"] | |
| for path_name, path_data in paths.items(): | |
| for k in path_data.keys(): | |
| if path_data[k] is not None: | |
| path_data[k] *= scale | |
| # List SDF files | |
| files = [f for f in os.listdir(sdf_dir) if f.lower().endswith(".sdf")] | |
| # Read and parse | |
| cell_timings = {} | |
| for f in files: | |
| print("Loading SDF: '{}'".format(f)) | |
| # Read | |
| fname = os.path.join(sdf_dir, f) | |
| with open(fname, "r") as fp: | |
| sdf = sdfparse.parse(fp.read()) | |
| # Get the timing scale | |
| header = sdf["header"] | |
| if "timescale" in header: | |
| timescale = get_scale_seconds(header["timescale"]) | |
| else: | |
| print("WARNING: the SDF has no timescale, assuming 1.0") | |
| timescale = 1.0 | |
| # Apply the scale and update cells | |
| cells = sdf["cells"] | |
| apply_scale(cells, timescale) | |
| cell_timings.update(cells) | |
| return cell_timings | |
| # ============================================================================= | |
| 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) | |
| # ============================================================================= | |
| if __name__ == "__main__": | |
| main() |