blob: 84dc69d2853c49422ad334e8d7a4f771547c7435 [file] [log] [blame] [edit]
#!/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()