blob: 5ae30fd25d3514c4599806dd2594b7579507ee91 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2022 F4PGA Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
import argparse
import lxml.etree as ET
from f4pga.utils.quicklogic.pp3.data_structs import SwitchboxPinType
from f4pga.utils.quicklogic.pp3.data_import import import_data
# =============================================================================
def fixup_pin_name(name):
return name.replace("[", "").replace("]", "")
def switchbox_to_dot(switchbox, stage_types=("STREET", "HIGHWAY")):
dot = []
TYPE_TO_COLOR = {
SwitchboxPinType.UNSPEC: "#C0C0C0",
SwitchboxPinType.LOCAL: "#C0C0FF",
SwitchboxPinType.HOP: "#FFFFC0",
SwitchboxPinType.CONST: "#FFC0C0",
SwitchboxPinType.GCLK: "#C0FFC0",
}
# Add header
dot.append("digraph {} {{".format(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 [shape=record, style=filled, fillcolor=white];")
stage_ids_to_show = set([s.id for s in switchbox.stages.values() if s.type in stage_types])
# Top-level inputs
dot.append(" subgraph cluster_inputs {")
dot.append(" node [shape=ellipse, style=filled];")
dot.append(' label="Inputs";')
for pin in switchbox.inputs.values():
stage_ids = set([loc.stage_id for loc in pin.locs])
if not len(stage_ids & stage_ids_to_show):
continue
color = TYPE_TO_COLOR.get(pin.type, "#C0C0C0")
name = "input_{}".format(fixup_pin_name(pin.name))
dot.append(' {} [rank=0, label="{}", fillcolor="{}"];'.format(name, pin.name, color))
dot.append(" }")
# Top-level outputs
dot.append(" subgraph cluster_outputs {")
dot.append(" node [shape=ellipse, style=filled];")
dot.append(' label="Outputs";')
rank = max(switchbox.stages.keys()) + 1
for pin in switchbox.outputs.values():
stage_ids = set([loc.stage_id for loc in pin.locs])
if not len(stage_ids & stage_ids_to_show):
continue
color = TYPE_TO_COLOR[pin.type]
name = "output_{}".format(fixup_pin_name(pin.name))
dot.append(' {} [rank={}, label="{}", fillcolor="{}"];'.format(name, rank, pin.name, color))
dot.append(" }")
# Stages
for stage in switchbox.stages.values():
if stage.type not in stage_types:
continue
rank = stage.id + 1
dot.append(" subgraph cluster_st{} {{".format(stage.id))
dot.append(" label=\"Stage #{} '{}'\";".format(stage.id, stage.type))
dot.append(' bgcolor="#D0D0D0"')
# Switch
for switch in stage.switches.values():
dot.append(" subgraph cluster_st{}_sw{} {{".format(stage.id, switch.id))
dot.append(' label="Switch #{}";'.format(switch.id))
dot.append(' bgcolor="#F0F0F0"')
# Mux
for mux in switch.muxes.values():
inputs = sorted(mux.inputs.values(), key=lambda p: p.id)
mux_l = "Mux #{}".format(mux.id)
inp_l = "|".join(["<i{}> {}. {}".format(p.id, p.id, p.name) for p in inputs])
out_l = "<o{}> {}. {}".format(mux.output.id, mux.output.id, mux.output.name)
label = "{}|{{{{{}}}|{{{}}}}}".format(mux_l, inp_l, out_l)
name = "st{}_sw{}_mx{}".format(stage.id, switch.id, mux.id)
dot.append(' {} [rank="{}", label="{}"];'.format(name, rank, label))
dot.append(" }")
dot.append(" }")
# Internal connections
for conn in switchbox.connections:
if switchbox.stages[conn.src.stage_id].type not in stage_types:
continue
if switchbox.stages[conn.dst.stage_id].type not in stage_types:
continue
src_node = "st{}_sw{}_mx{}".format(conn.src.stage_id, conn.src.switch_id, conn.src.mux_id)
src_port = "o{}".format(conn.src.pin_id)
dst_node = "st{}_sw{}_mx{}".format(conn.dst.stage_id, conn.dst.switch_id, conn.dst.mux_id)
dst_port = "i{}".format(conn.dst.pin_id)
dot.append(" {}:{} -> {}:{};".format(src_node, src_port, dst_node, dst_port))
# Input pin connections
for pin in switchbox.inputs.values():
src_node = "input_{}".format(fixup_pin_name(pin.name))
for loc in pin.locs:
if switchbox.stages[loc.stage_id].type not in stage_types:
continue
dst_node = "st{}_sw{}_mx{}".format(loc.stage_id, loc.switch_id, loc.mux_id)
dst_port = "i{}".format(loc.pin_id)
dot.append(" {} -> {}:{};".format(src_node, dst_node, dst_port))
# Output pin connections
for pin in switchbox.outputs.values():
dst_node = "output_{}".format(fixup_pin_name(pin.name))
for loc in pin.locs:
if switchbox.stages[loc.stage_id].type not in stage_types:
continue
src_node = "st{}_sw{}_mx{}".format(loc.stage_id, loc.switch_id, loc.mux_id)
src_port = "o{}".format(loc.pin_id)
dot.append(" {}:{} -> {};".format(src_node, src_port, dst_node))
# Footer
dot.append("}")
return "\n".join(dot)
# =============================================================================
def main():
# Parse arguments
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("i", type=str, help="Quicklogic 'TechFile' file")
parser.add_argument(
"--stages", type=str, default="STREET", help="Comma-separated list of stage types to view (def. STREET)"
)
args = parser.parse_args()
# Read and parse the XML file
xml_tree = ET.parse(args.i)
xml_root = xml_tree.getroot()
# Load data
data = import_data(xml_root)
switchbox_types = data["switchbox_types"]
# Generate DOT files with switchbox visualizations
for switchbox in switchbox_types.values():
fname = "sbox_{}.dot".format(switchbox.type)
with open(fname, "w") as fp:
fp.write(switchbox_to_dot(switchbox, args.stages.split(",")))
# =============================================================================
if __name__ == "__main__":
main()