| #!/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() |