blob: 8055f8b78b4a35285ea04c046cd77bbd000eea2d [file] [log] [blame] [edit]
#!/usr/bin/env python3
"""
This utility generates a FASM file with a default bitstream configuration for
the given device.
"""
import argparse
import colorsys
from enum import Enum
import lxml.etree as ET
from data_structs import PinDirection, SwitchboxPinType
from data_import import import_data
from utils import yield_muxes
from switchbox_model import SwitchboxModel
# =============================================================================
duplicate = {}
class SwitchboxConfigBuilder:
"""
This class is responsible for routing a switchbox according to the
requested parameters and writing FASM features that configure it.
"""
class NodeType(Enum):
MUX = 0
SOURCE = 1
SINK = 2
class Node:
"""
Represents a graph node that corresponds either to a switchbox mux
output or to a virtual source / sink node.
"""
def __init__(self, type, key):
self.type = type
self.key = key
# Current "net"
self.net = None
# Mux input ids indexed by keys and mux selection
self.inp = {}
self.sel = None
# Mux inputs driven by this node as keys
self.out = set()
def __init__(self, switchbox):
self.switchbox = switchbox
self.nodes = {}
# Build nodes representing the switchbox connectivity graph
self._build_nodes()
def _build_nodes(self):
"""
Creates all nodes for routing.
"""
# Create all mux nodes
for stage, switch, mux in yield_muxes(self.switchbox):
# Create the node
key = (stage.id, switch.id, mux.id)
node = self.Node(self.NodeType.MUX, key)
# Store the node
if stage.type not in self.nodes:
self.nodes[stage.type] = {}
assert node.key not in self.nodes[stage.type
], (stage.type, node.key)
self.nodes[stage.type][node.key] = node
# Create all source and sink nodes, populate their connections with mux
# nodes.
for pin in self.switchbox.pins:
# Node type
if pin.direction == PinDirection.INPUT:
node_type = self.NodeType.SOURCE
elif pin.direction == PinDirection.OUTPUT:
node_type = self.NodeType.SINK
else:
assert False, node_type
# Create one for each stage type
stage_ids = set([loc.stage_id for loc in pin.locs])
for stage_id in stage_ids:
# Create the node
key = pin.name
node = self.Node(node_type, key)
# Initially annotate source nodes with net names
if node.type == self.NodeType.SOURCE:
node.net = pin.name
# Get the correct node list
stage_type = self.switchbox.stages[stage_id].type
assert stage_type in self.nodes, stage_type
nodes = self.nodes[stage_type]
# Add the node
assert node.key not in self.nodes, node.key
nodes[node.key] = node
# Populate connections
for pin_loc in pin.locs:
# Get the correct node list
stage_type = self.switchbox.stages[pin_loc.stage_id].type
assert stage_type in self.nodes, stage_type
nodes = self.nodes[stage_type]
if pin.direction == PinDirection.INPUT:
# Get the mux node
key = (pin_loc.stage_id, pin_loc.switch_id, pin_loc.mux_id)
assert key in nodes, key
node = nodes[key]
key = (
self.switchbox.type, pin_loc.stage_id,
pin_loc.switch_id, pin_loc.mux_id
)
if (key in duplicate # Mux has multiple inputs selected
and (pin_loc.pin_id in duplicate[key]
) # Current selection is duplicate
and not (key[0].startswith("SB_TOP_IFC"))
): # Ignore TOP switchboxes
print(
"Warning: duplicate: {} - {}".format(
key, pin_loc.pin_id
)
)
continue
# Append reference to the input pin to the node
key = pin.name
assert key == "GND" or key not in node.inp, key
node.inp[key] = pin_loc.pin_id
# Get the SOURCE node
key = pin.name
assert key in nodes, key
node = nodes[key]
# Append the mux node as a sink
key = (pin_loc.stage_id, pin_loc.switch_id, pin_loc.mux_id)
node.out.add(key)
elif pin.direction == PinDirection.OUTPUT:
# Get the sink node
key = pin.name
assert key in nodes, key
node = nodes[key]
assert node.type == self.NodeType.SINK
# Append reference to the mux
key = (pin_loc.stage_id, pin_loc.switch_id, pin_loc.mux_id)
node.inp[key] = 0
# Get the mux node
key = (pin_loc.stage_id, pin_loc.switch_id, pin_loc.mux_id)
assert key in nodes, key
node = nodes[key]
# Append the sink as the mux sink
key = pin.name
node.out.add(key)
else:
assert False, pin.direction
# Populate mux to mux connections
for conn in self.switchbox.connections:
# Get the correct node list
stage_type = self.switchbox.stages[conn.dst.stage_id].type
assert stage_type in self.nodes, stage_type
nodes = self.nodes[stage_type]
# Get the node
key = (conn.dst.stage_id, conn.dst.switch_id, conn.dst.mux_id)
assert key in nodes, key
node = nodes[key]
# Add its input and pin index
key = (conn.src.stage_id, conn.src.switch_id, conn.src.mux_id)
node.inp[key] = conn.dst.pin_id
# Get the source node
key = (conn.src.stage_id, conn.src.switch_id, conn.src.mux_id)
assert key in nodes, key
node = nodes[key]
# Add the destination node to its outputs
key = (conn.dst.stage_id, conn.dst.switch_id, conn.dst.mux_id)
node.out.add(key)
def stage_inputs(self, stage_type):
"""
Yields inputs of the given stage type
"""
assert stage_type in self.nodes, stage_type
for node in self.nodes[stage_type].values():
if node.type == self.NodeType.SOURCE:
yield node.key
def stage_outputs(self, stage_type):
"""
Yields outputs of the given stage type
"""
assert stage_type in self.nodes, stage_type
for node in self.nodes[stage_type].values():
if node.type == self.NodeType.SINK:
yield node.key
def propagate_input(self, stage_type, input_name):
"""
Recursively propagates a net from an input pin to all reachable
mux / sink nodes.
"""
# Get the correct node list
assert stage_type in self.nodes, stage_type
nodes = self.nodes[stage_type]
def walk(node):
# Examine all driven nodes
for sink_key in node.out:
assert sink_key in nodes, sink_key
sink_node = nodes[sink_key]
# The sink is free
if sink_node.net is None:
# Assign it to the net
sink_node.net = node.net
if sink_node.type == self.NodeType.MUX:
sink_node.sel = sink_node.inp[node.key]
# Expand
walk(sink_node)
# Find the source node
assert input_name in nodes, input_name
node = nodes[input_name]
# Walk downstream
node.net = input_name
walk(node)
def ripup(self, stage_type):
"""
Rips up all routes within the given stage
"""
assert stage_type in self.nodes, stage_type
for node in self.nodes[stage_type].values():
if node.type != self.NodeType.SOURCE:
node.net = None
node.sel = None
def check_nodes(self):
"""
Check if all mux nodes have their selections set
"""
result = True
for stage_type, nodes in self.nodes.items():
for key, node in nodes.items():
if node.type == self.NodeType.MUX and node.sel is None:
result = False
print("WARNING: mux unconfigured", key)
return result
def fasm_features(self, loc):
"""
Returns a list of FASM lines that correspond to the routed switchbox
configuration.
"""
lines = []
for stage_type, nodes in self.nodes.items():
for key, node in nodes.items():
# For muxes with active selection
if node.type == self.NodeType.MUX and node.sel is not None:
stage_id, switch_id, mux_id = key
# Get FASM features using the switchbox model.
features = SwitchboxModel.get_metadata_for_mux(
loc, self.switchbox.stages[stage_id], switch_id,
mux_id, node.sel
)
lines.extend(features)
return lines
def dump_dot(self):
"""
Dumps a routed switchbox visualization into Graphviz format for
debugging purposes.
"""
dot = []
def key2str(key):
if isinstance(key, str):
return key
else:
return "st{}_sw{}_mx{}".format(*key)
def fixup_label(lbl):
lbl = lbl.replace("[", "(").replace("]", ")")
# All nets
nets = set()
for nodes in self.nodes.values():
for node in nodes.values():
if node.net is not None:
nets.add(node.net)
# Net colors
node_colors = {None: "#C0C0C0"}
edge_colors = {None: "#000000"}
nets = sorted(list(nets))
for i, net in enumerate(nets):
hue = i / len(nets)
light = 0.33
saturation = 1.0
r, g, b = colorsys.hls_to_rgb(hue, light, saturation)
color = "#{:02X}{:02X}{:02X}".format(
int(r * 255.0),
int(g * 255.0),
int(b * 255.0),
)
node_colors[net] = color
edge_colors[net] = color
# Add header
dot.append("digraph {} {{".format(self.switchbox.type))
dot.append(" graph [nodesep=\"1.0\", ranksep=\"20\"];")
dot.append(" splines = \"false\";")
dot.append(" rankdir = LR;")
dot.append(" margin = 20;")
dot.append(" node [style=filled];")
# Stage types
for stage_type, nodes in self.nodes.items():
# Stage header
dot.append(" subgraph \"cluster_{}\" {{".format(stage_type))
dot.append(" label=\"Stage '{}'\";".format(stage_type))
# Nodes and internal mux edges
for key, node in nodes.items():
# Source node
if node.type == self.NodeType.SOURCE:
name = "{}_inp_{}".format(stage_type, key2str(key))
label = key
color = node_colors[node.net]
dot.append(
" \"{}\" [shape=octagon label=\"{}\" fillcolor=\"{}\"];"
.format(
name,
label,
color,
)
)
# Sink node
elif node.type == self.NodeType.SINK:
name = "{}_out_{}".format(stage_type, key2str(key))
label = key
color = node_colors[node.net]
dot.append(
" \"{}\" [shape=octagon label=\"{}\" fillcolor=\"{}\"];"
.format(
name,
label,
color,
)
)
# Mux node
elif node.type == self.NodeType.MUX:
name = "{}_{}".format(stage_type, key2str(key))
dot.append(" subgraph \"cluster_{}\" {{".format(name))
dot.append(
" label=\"{}, sel={}\";".format(
str(key), node.sel
)
)
# Inputs
for drv_key, pin in node.inp.items():
if node.sel == pin:
assert drv_key in nodes, drv_key
net = nodes[drv_key].net
else:
net = None
name = "{}_{}_{}".format(stage_type, key2str(key), pin)
label = pin
color = node_colors[net]
dot.append(
" \"{}\" [shape=ellipse label=\"{}\" fillcolor=\"{}\"];"
.format(
name,
label,
color,
)
)
# Output
name = "{}_{}".format(stage_type, key2str(key))
label = "out"
color = node_colors[node.net]
dot.append(
" \"{}\" [shape=ellipse label=\"{}\" fillcolor=\"{}\"];"
.format(
name,
label,
color,
)
)
# Internal mux edges
for drv_key, pin in node.inp.items():
if node.sel == pin:
assert drv_key in nodes, drv_key
net = nodes[drv_key].net
else:
net = None
src_name = "{}_{}_{}".format(
stage_type, key2str(key), pin
)
dst_name = "{}_{}".format(stage_type, key2str(key))
color = edge_colors[net]
dot.append(
" \"{}\" -> \"{}\" [color=\"{}\"];".format(
src_name,
dst_name,
color,
)
)
dot.append(" }")
else:
assert False, node.type
# Mux to mux connections
for key, node in nodes.items():
# Source node
if node.type == self.NodeType.SOURCE:
pass
# Sink node
elif node.type == self.NodeType.SINK:
assert len(node.inp) == 1, node.inp
src_key = next(iter(node.inp.keys()))
dst_name = "{}_out_{}".format(stage_type, key2str(key))
if isinstance(src_key, str):
src_name = "{}_inp_{}".format(
stage_type, key2str(src_key)
)
else:
src_name = "{}_{}".format(stage_type, key2str(src_key))
color = node_colors[node.net]
dot.append(
" \"{}\" -> \"{}\" [color=\"{}\"];".format(
src_name,
dst_name,
color,
)
)
# Mux node
elif node.type == self.NodeType.MUX:
for drv_key, pin in node.inp.items():
if node.sel == pin:
assert drv_key in nodes, drv_key
net = nodes[drv_key].net
else:
net = None
dst_name = "{}_{}_{}".format(
stage_type, key2str(key), pin
)
if isinstance(drv_key, str):
src_name = "{}_inp_{}".format(
stage_type, key2str(drv_key)
)
else:
src_name = "{}_{}".format(
stage_type, key2str(drv_key)
)
color = edge_colors[net]
dot.append(
" \"{}\" -> \"{}\" [color=\"{}\"];".format(
src_name,
dst_name,
color,
)
)
else:
assert False, node.type
# Stage footer
dot.append(" }")
# Add footer
dot.append("}")
return "\n".join(dot)
# =============================================================================
def main():
# Parse arguments
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
"--techfile",
type=str,
required=True,
help="Quicklogic 'TechFile' XML file"
)
parser.add_argument(
"--fasm",
type=str,
default="default.fasm",
help="Output FASM file name"
)
parser.add_argument(
"--device",
type=str,
choices=["eos-s3"],
default="eos-s3",
help="Device name to generate the FASM file for"
)
parser.add_argument(
"--dump-dot",
action="store_true",
help="Dump Graphviz .dot files for each routed switchbox type"
)
parser.add_argument(
"--allow-routing-failures",
action="store_true",
help="Skip switchboxes that fail routing"
)
args = parser.parse_args()
# Read and parse the XML file
xml_tree = ET.parse(args.techfile)
xml_root = xml_tree.getroot()
# Load data
print("Loading data from the techfile...")
data = import_data(xml_root)
switchbox_types = data["switchbox_types"]
switchbox_grid = data["switchbox_grid"]
tile_types = data["tile_types"]
tile_grid = data["tile_grid"]
# Route switchboxes
print("Making switchbox routes...")
fasm = []
fully_routed = 0
partially_routed = 0
def input_rank(pin):
"""
Returns a rank of a switchbox input. Pins with the lowest rank should
be expanded first.
"""
if pin.name == "GND":
return 0
elif pin.name == "VCC":
return 1
elif pin.type not in [SwitchboxPinType.HOP, SwitchboxPinType.GCLK]:
return 2
elif pin.type == SwitchboxPinType.HOP:
return 3
elif pin.type == SwitchboxPinType.GCLK:
return 4
return 99
# Scan for duplicates
for switchbox in switchbox_types.values():
for pin in switchbox.pins:
pinmap = {}
for pin_loc in pin.locs:
key = (
switchbox.type, pin_loc.stage_id, pin_loc.switch_id,
pin_loc.mux_id
)
if (key not in pinmap):
pinmap[key] = pin_loc.pin_id
else:
if key in duplicate:
duplicate[key].append(pin_loc.pin_id)
else:
duplicate[key] = [pin_loc.pin_id]
# Process each switchbox type
for switchbox in switchbox_types.values():
print("", switchbox.type)
# Identify all locations of the switchbox
locs = [
loc for loc, type in switchbox_grid.items()
if type == switchbox.type
]
# Initialize the builder
builder = SwitchboxConfigBuilder(switchbox)
# Sort the inputs according to their ranks.
inputs = sorted(switchbox.inputs.values(), key=input_rank)
# Propagate them
for stage in ["STREET", "HIGHWAY"]:
for pin in inputs:
if pin.name in builder.stage_inputs(stage):
builder.propagate_input(stage, pin.name)
# Check if all nodes are configured
routing_failed = not builder.check_nodes()
# Dump dot
if args.dump_dot:
dot = builder.dump_dot()
fname = "defconfig_{}.dot".format(switchbox.type)
with open(fname, "w") as fp:
fp.write(dot)
# Routing failed
if routing_failed:
if not args.allow_routing_failures:
exit(-1)
# Stats
if routing_failed:
partially_routed += len(locs)
else:
fully_routed += len(locs)
# Emit FASM features for each of them
for loc in locs:
fasm.extend(builder.fasm_features(loc))
print(" Total switchboxes: {}".format(len(switchbox_grid)))
print(" Fully routed : {}".format(fully_routed))
print(" Partially routed : {}".format(partially_routed))
# Power on all LOGIC cells
for loc, tile in tile_grid.items():
# Get the tile type object
tile_type = tile_types[tile.type]
# If this tile has a LOGIC cell then emit the FASM feature that
# enables its power
if "LOGIC" in tile_type.cells:
feature = "X{}Y{}.LOGIC.LOGIC.Ipwr_gates.J_pwr_st".format(
loc.x, loc.y
)
fasm.append(feature)
# Write FASM
print("Writing FASM file...")
with open(args.fasm, "w") as fp:
fp.write("\n".join(fasm))
# =============================================================================
if __name__ == "__main__":
main()