blob: b5b3428f9dd069f8111152e24ecee13e3d5d9d4a [file] [log] [blame] [edit]
"""
Utility functions for making hop connections between switchboxes and locat
switchbox - tile connections.
"""
import re
from data_structs import SwitchboxPinType, Loc, OPPOSITE_DIRECTION, \
Connection, ConnectionLoc, ConnectionType, PinDirection
from utils import find_cell_in_tile
# =============================================================================
# A regex for regular HOP wires
RE_HOP_WIRE = re.compile(r"^([HV])([0-9])([TBLR])([0-9])")
# A regex for HOP offsets
RE_HOP = re.compile(r"^([\[\]A-Za-z0-9_]+)_([TBLR])([0-9]+)$")
# =============================================================================
def hop_to_str(offset):
"""
Formats a two-character string that uniquely identifies the hop offset.
>>> hop_to_str([-3, 0])
'L3'
>>> hop_to_str([1, 0])
'R1'
>>> hop_to_str([0, -2])
'T2'
>>> hop_to_str([0, +7])
'B7'
"""
# Zero offsets are not allowed
assert offset[0] != 0 or offset[1] != 0, offset
# Diagonal offsets are not allowed
if offset[0] != 0:
assert offset[1] == 0, offset
if offset[1] != 0:
assert offset[0] == 0, offset
# Horizontal
if offset[1] == 0:
if offset[0] > 0:
return "R{}".format(+offset[0])
if offset[0] < 0:
return "L{}".format(-offset[0])
# Vertical
if offset[0] == 0:
if offset[1] > 0:
return "B{}".format(+offset[1])
if offset[1] < 0:
return "T{}".format(-offset[1])
# Should not happen
assert False, offset
def get_name_and_hop(name):
"""
Extracts wire name and hop offset given a hop wire name. Returns a tuple
with (name, (hop_x, hop_y)).
When a wire name does not contain hop definition then the function
returns (name, None).
Note: the hop offset is defined from the input (destination) perspective.
>>> get_name_and_hop("WIRE")
('WIRE', None)
>>> get_name_and_hop("V4T0_B3")
('V4T0', (0, 3))
>>> get_name_and_hop("H2R1_L1")
('H2R1', (-1, 0))
>>> get_name_and_hop("RAM_A[5]_T2")
('RAM_A[5]', (0, -2))
"""
# Check if the name defines a hop.
match = RE_HOP.match(name)
if match is None:
return name, None
# Hop length
length = int(match.group(3))
assert length in [1, 2, 3, 4], (name, length)
# Hop direction
direction = match.group(2)
if direction == "T":
hop = (0, -length)
elif direction == "B":
hop = (0, +length)
elif direction == "L":
hop = (-length, 0)
elif direction == "R":
hop = (+length, 0)
else:
assert False, (name, direction)
return match.group(1), hop
def is_regular_hop_wire(name):
"""
Returns True if the wire name defines a regular HOP wire. Also performs
sanity checks of the name.
>>> is_regular_hop_wire("H1R5")
True
>>> is_regular_hop_wire("V4B7")
True
>>> is_regular_hop_wire("WIRE")
False
>>> is_regular_hop_wire("MULT_Addr[17]_R3")
False
"""
# Match
match = RE_HOP_WIRE.match(name)
if match is None:
return False
# Check length
length = int(match.group(2))
assert length in [1, 2, 4], name
# Check orientation
orientation = match.group(1)
direction = match.group(3)
if orientation == "H":
assert direction in ["L", "R"], name
if orientation == "V":
assert direction in ["T", "B"], name
return True
# =============================================================================
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 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 find_clock_cell(alias, tile_grid):
for loc, tile in tile_grid.items():
if tile is None:
continue
# Must have at least one "CLOCK" cell
clock_cells = [c for c in tile.cells if c.type == "CLOCK"]
if len(clock_cells) == 0:
continue
for cell in clock_cells:
if cell.alias == alias:
return loc, tile, cell
return None, None, None
def build_gmux_qmux_connections(
tile_types, tile_grid, switchbox_types, switchbox_grid, clock_cells
):
# Define names of all global clock wires.
# Each global clock mux as an implicitly defined output equal to its name.
clock_wires = list(clock_cells.keys())
# GMUX "IP" inputs are global clock wires too
for clock_mux in clock_cells.values():
if clock_mux.type == "GMUX":
clock_wires.append(clock_mux.pin_map["IP"])
# Conections between clock cells (muxes)
connections = []
for clock_cell in clock_cells.values():
for pin_name, pin_conn in clock_cell.pin_map.items():
# Destination pin name. Treat CAND and QMUX destinations
# differently as there are going to be no tiles for them.
if clock_cell.type in ["CAND", "QMUX"]:
dst_type = ConnectionType.CLOCK
dst_pin_name = "{}.{}".format(clock_cell.name, pin_name)
else:
dst_tile = tile_grid[clock_cell.loc]
dst_cell = find_cell_in_tile(clock_cell.name, dst_tile)
dst_type = ConnectionType.TILE
dst_pin_name = "{}{}_{}".format(
dst_cell.type, dst_cell.index, pin_name
)
# This pin connects to a global clock wire
if pin_conn in clock_wires:
# Get the other cell
other_cell = clock_cells.get(pin_conn, None)
# Not found in the clock cells. Probably it is the CLOCK cell
# try finding it by its name / alias
if other_cell is None:
src_loc, src_tile, src_cell = \
find_clock_cell(pin_conn, tile_grid)
# Didint find the cell
if src_cell is None:
print(
"WARNING: No source cell for global clock wire '{}'"
.format(pin_conn)
)
continue
# Connect to the cell
src_type = ConnectionType.TILE
src_pin_name = "{}{}_{}".format(
src_cell.type, src_cell.index, "IC"
)
is_direct = True
# Connect to the other cell
else:
src_loc = other_cell.loc
# Source pin name. Tread CAND and QMUX differently as there
# are going to be no tiles for them.
if other_cell.type in ["CAND", "QMUX"]:
src_type = ConnectionType.CLOCK
src_pin_name = "{}.{}".format(other_cell.name, "IZ")
else:
src_tile = tile_grid[other_cell.loc]
src_cell = find_cell_in_tile(other_cell.name, src_tile)
src_type = ConnectionType.TILE
src_pin_name = "{}{}_{}".format(
src_cell.type, src_cell.index, "IZ"
)
is_direct = False
# Make the connection
connections.append(
Connection(
src=ConnectionLoc(
loc=src_loc, pin=src_pin_name, type=src_type
),
dst=ConnectionLoc(
loc=clock_cell.loc,
pin=dst_pin_name,
type=dst_type
),
is_direct=is_direct
)
)
return connections
# =============================================================================
def build_connections(
tile_types, tile_grid, switchbox_types, switchbox_grid, clock_cells
):
"""
Builds a connection map between switchboxes in the grid and between
switchboxes and underlying tiles.
"""
connections = []
# Local and foreign tile connections
connections += build_tile_connections(
tile_types, tile_grid, switchbox_types, switchbox_grid
)
# HOP connections
connections += build_hop_connections(switchbox_types, switchbox_grid)
# GMUX and QMUX connections
connections += build_gmux_qmux_connections(
tile_types, tile_grid, switchbox_types, switchbox_grid, clock_cells
)
return connections
def check_connections(connections):
"""
Check if all connections are sane.
- All connections should be point-to-point. No fanin/fanouts.
"""
error = False
# Check if there are no duplicated connections going to the same destination
dst_conn_locs = set()
for connection in connections:
if connection.dst in dst_conn_locs:
error = True
print("ERROR: Duplicate destination '{}'".format(connection.dst))
dst_conn_locs.add(connection.dst)
# An error ocurred
assert error is False