|  | #!/usr/bin/env python3 | 
|  | """ | 
|  | This script allows to read the tile grid from various sources and render it to | 
|  | either SVG or PDF file. It can also draw connections between tiles when | 
|  | provided with the 'tileconn.json' file used in the prjxray database. | 
|  |  | 
|  | Use ONE of following argument sets for data source specification: | 
|  | 1. --tilgrid <tilegrid.json> [--tileconn <tileconn.json>] | 
|  | 2. --arch-xml <arch.xml> | 
|  | 3. --graph-xml <rr_graph.xml> | 
|  | 4. --conn-db <channels.db> [--tb-table <tile table name>] | 
|  | """ | 
|  |  | 
|  | import sys | 
|  | import argparse | 
|  | import os | 
|  | import re | 
|  | from collections import namedtuple | 
|  |  | 
|  | import progressbar | 
|  |  | 
|  | import json | 
|  | import sqlite3 | 
|  |  | 
|  | import lxml.etree as ET | 
|  | import lxml.objectify as objectify | 
|  |  | 
|  | import svgwrite | 
|  |  | 
|  | # ============================================================================= | 
|  |  | 
|  |  | 
|  | class GridVisualizer(object): | 
|  |  | 
|  | BLOCK_RECT = 100 | 
|  | BLOCK_GAP = 10 | 
|  | BLOCK_SIZE = BLOCK_RECT + BLOCK_GAP | 
|  |  | 
|  | Loc = namedtuple("Loc", "x y") | 
|  | Conn = namedtuple("Conn", "loc0 loc1") | 
|  | GridExtent = namedtuple("GridExtent", "xmin ymin xmax ymax") | 
|  |  | 
|  | def __init__(self): | 
|  |  | 
|  | self.tilegrid = None | 
|  | self.tileconn = None | 
|  |  | 
|  | self.grid_roi = None | 
|  | self.conn_roi = None | 
|  |  | 
|  | self.tile_colormap = None | 
|  | self.connections = {} | 
|  |  | 
|  | def load_tilegrid_from_json(self, json_file): | 
|  |  | 
|  | # Load JSON files | 
|  | with open(json_file, "r") as fp: | 
|  | self.tilegrid = json.load(fp) | 
|  |  | 
|  | self._determine_grid_extent() | 
|  | self._build_loc_map() | 
|  |  | 
|  | def load_tileconn_from_json(self, json_file): | 
|  |  | 
|  | # Load JSON files | 
|  | with open(json_file, "r") as fp: | 
|  | self.tileconn = json.load(fp) | 
|  |  | 
|  | self._form_connections() | 
|  |  | 
|  | def load_tilegrid_from_arch_xml(self, xml_file): | 
|  |  | 
|  | # Load and parse the XML | 
|  | parser = ET.XMLParser(remove_comments=True) | 
|  | xml_tree = objectify.parse(xml_file, parser=parser) | 
|  | xml_root = xml_tree.getroot() | 
|  |  | 
|  | # Get the layout section | 
|  | layout = xml_root.find("layout") | 
|  | assert (layout is not None) | 
|  |  | 
|  | # Get the fixed_layout section | 
|  | fixed_layout = layout.find("fixed_layout") | 
|  | assert (fixed_layout is not None) | 
|  |  | 
|  | # Extract the grid extent | 
|  | dx = int(fixed_layout.get("width")) | 
|  | dy = int(fixed_layout.get("height")) | 
|  | self.grid_extent = self.GridExtent(0, 0, dx, dy) | 
|  |  | 
|  | # Convert | 
|  | self.tilegrid = {} | 
|  |  | 
|  | for tile in list(fixed_layout): | 
|  | assert (tile.tag == "single") | 
|  |  | 
|  | # Basic tile parameters | 
|  | grid_x = int(tile.get("x")) | 
|  | grid_y = int(tile.get("y")) | 
|  | tile_type = tile.get("type") | 
|  |  | 
|  | # Tile name (if present) | 
|  | tile_name = None | 
|  | metadata = tile.find("metadata") | 
|  | if metadata is not None: | 
|  | for meta in metadata.findall("meta"): | 
|  | if meta.get("name") == "fasm_prefix": | 
|  | tile_name = meta.text | 
|  |  | 
|  | # Fake tile name | 
|  | if tile_name is None: | 
|  | tile_name = "UNKNOWN_X%dY%d" % (grid_x, grid_y) | 
|  |  | 
|  | # Already exists | 
|  | if tile_name in self.tilegrid: | 
|  | tile_name += "(2)" | 
|  |  | 
|  | self.tilegrid[tile_name] = { | 
|  | "grid_x": grid_x, | 
|  | "grid_y": grid_y, | 
|  | "type": tile_type | 
|  | } | 
|  |  | 
|  | self._build_loc_map() | 
|  |  | 
|  | def load_tilegrid_from_graph_xml(self, xml_file): | 
|  |  | 
|  | # Load and parse the XML | 
|  | parser = ET.XMLParser(remove_comments=True) | 
|  | xml_tree = objectify.parse(xml_file, parser=parser) | 
|  | xml_root = xml_tree.getroot() | 
|  |  | 
|  | # Load block types | 
|  | xml_block_types = xml_root.find("block_types") | 
|  | assert (xml_block_types is not None) | 
|  |  | 
|  | block_types = {} | 
|  | for xml_block_type in xml_block_types: | 
|  | block_type_id = int(xml_block_type.get("id")) | 
|  | block_name = xml_block_type.get("name") | 
|  |  | 
|  | block_types[block_type_id] = block_name | 
|  |  | 
|  | # Load grid | 
|  | self.tilegrid = {} | 
|  |  | 
|  | all_x = set() | 
|  | all_y = set() | 
|  |  | 
|  | xml_grid = xml_root.find("grid") | 
|  | assert (xml_grid is not None) | 
|  |  | 
|  | for xml_grid_loc in xml_grid: | 
|  | grid_x = int(xml_grid_loc.get("x")) | 
|  | grid_y = int(xml_grid_loc.get("y")) | 
|  | block_type_id = int(xml_grid_loc.get("block_type_id")) | 
|  |  | 
|  | all_x.add(grid_x) | 
|  | all_y.add(grid_y) | 
|  |  | 
|  | # Fake tile name | 
|  | tile_name = "BLOCK_X%dY%d" % (grid_x, grid_y) | 
|  |  | 
|  | self.tilegrid[tile_name] = { | 
|  | "grid_x": grid_x, | 
|  | "grid_y": grid_y, | 
|  | "type": block_types[block_type_id] | 
|  | } | 
|  |  | 
|  | # Determine grid extent | 
|  | self.grid_extent = self.GridExtent( | 
|  | min(all_x), min(all_y), max(all_x), max(all_y) | 
|  | ) | 
|  |  | 
|  | self._build_loc_map() | 
|  |  | 
|  | def load_tilegrid_from_conn_db(self, db_file, db_table): | 
|  |  | 
|  | # Connect to the database and load data | 
|  | with sqlite3.Connection("file:%s?mode=ro" % db_file, uri=True) as conn: | 
|  | c = conn.cursor() | 
|  |  | 
|  | # Load the grid | 
|  | db_tiles = c.execute( | 
|  | "SELECT pkey, name, tile_type_pkey, grid_x, grid_y FROM %s" % | 
|  | (db_table) | 
|  | ).fetchall()  # They say that it is insecure.. | 
|  |  | 
|  | # Load tile types | 
|  | db_tile_types = c.execute("SELECT pkey, name FROM tile_type" | 
|  | ).fetchall() | 
|  |  | 
|  | # Maps pkey to type string | 
|  | tile_type_map = {} | 
|  | for item in db_tile_types: | 
|  | tile_type_map[item[0]] = item[1] | 
|  |  | 
|  | # Translate information | 
|  | self.tilegrid = {} | 
|  |  | 
|  | all_x = set() | 
|  | all_y = set() | 
|  |  | 
|  | for tile in db_tiles: | 
|  |  | 
|  | tile_type_pkey = tile[2] | 
|  |  | 
|  | if tile_type_pkey not in tile_type_map.keys(): | 
|  | print("Unknown tile type pkey %d !" % tile_type_pkey) | 
|  | continue | 
|  |  | 
|  | tile_name = tile[1] | 
|  | tile_type = tile_type_map[tile_type_pkey] | 
|  | tile_grid_x = tile[3] | 
|  | tile_grid_y = tile[4] | 
|  |  | 
|  | if tile_name in self.tilegrid: | 
|  | print("Duplicate tile name '%s' !" % tile_name) | 
|  | continue | 
|  |  | 
|  | all_x.add(tile_grid_x) | 
|  | all_y.add(tile_grid_y) | 
|  |  | 
|  | self.tilegrid[tile_name] = { | 
|  | "grid_x": tile_grid_x, | 
|  | "grid_y": tile_grid_y, | 
|  | "type": tile_type | 
|  | } | 
|  |  | 
|  | # Determine grid extent | 
|  | self.grid_extent = self.GridExtent( | 
|  | min(all_x), min(all_y), max(all_x), max(all_y) | 
|  | ) | 
|  |  | 
|  | self._build_loc_map() | 
|  |  | 
|  | def load_tile_colormap(self, colormap): | 
|  |  | 
|  | # If it fails just skip it | 
|  | try: | 
|  | with open(colormap, "r") as fp: | 
|  | self.tile_colormap = json.load(fp) | 
|  |  | 
|  | except FileNotFoundError: | 
|  | pass | 
|  |  | 
|  | def set_grid_roi(self, roi): | 
|  | self.grid_roi = roi | 
|  |  | 
|  | def set_conn_roi(self, roi): | 
|  | self.conn_roi = roi | 
|  |  | 
|  | def _determine_grid_extent(self): | 
|  |  | 
|  | # Determine the grid extent | 
|  | xs = set() | 
|  | ys = set() | 
|  |  | 
|  | for tile in self.tilegrid.values(): | 
|  | xs.add(tile["grid_x"]) | 
|  | ys.add(tile["grid_y"]) | 
|  |  | 
|  | self.grid_extent = self.GridExtent(min(xs), min(ys), max(xs), max(ys)) | 
|  |  | 
|  | if self.grid_roi is not None: | 
|  | self.grid_extent = self.GridExtent( | 
|  | max(self.grid_extent.xmin, self.grid_roi[0]), | 
|  | max(self.grid_extent.ymin, self.grid_roi[1]), | 
|  | min(self.grid_extent.xmax, self.grid_roi[2]), | 
|  | min(self.grid_extent.ymax, self.grid_roi[3]) | 
|  | ) | 
|  |  | 
|  | def _build_loc_map(self): | 
|  |  | 
|  | self.loc_map = {} | 
|  |  | 
|  | for tile_name, tile in self.tilegrid.items(): | 
|  | loc = self.Loc(tile["grid_x"], tile["grid_y"]) | 
|  |  | 
|  | if loc in self.loc_map.keys(): | 
|  | print("Duplicate tile at [%d, %d] !" % (loc.x, loc.y)) | 
|  |  | 
|  | self.loc_map[loc] = tile_name | 
|  |  | 
|  | def _form_connections(self): | 
|  |  | 
|  | # Loop over tiles of interest | 
|  | print("Forming connections...") | 
|  | for tile_name, tile in progressbar.progressbar(self.tilegrid.items()): | 
|  |  | 
|  | this_loc = self.Loc(tile["grid_x"], tile["grid_y"]) | 
|  | this_type = tile["type"] | 
|  |  | 
|  | # Find matching connection rules | 
|  | for rule in self.tileconn: | 
|  | grid_deltas = rule["grid_deltas"] | 
|  | tile_types = rule["tile_types"] | 
|  | wire_count = len(rule["wire_pairs"]) | 
|  |  | 
|  | for k in [+1]: | 
|  |  | 
|  | # Get a couterpart tile according to the rule grid delta | 
|  | other_loc = self.Loc( | 
|  | this_loc.x + k * grid_deltas[0], | 
|  | this_loc.y + k * grid_deltas[1] | 
|  | ) | 
|  |  | 
|  | try: | 
|  | other_name = self.loc_map[other_loc] | 
|  | other_type = self.tilegrid[other_name]["type"] | 
|  | except KeyError: | 
|  | continue | 
|  |  | 
|  | # Check match | 
|  | if this_type == tile_types[0] and \ | 
|  | other_type == tile_types[1]: | 
|  |  | 
|  | # Add the connection | 
|  | conn = self.Conn(this_loc, other_loc) | 
|  |  | 
|  | if conn not in self.connections.keys(): | 
|  | self.connections[conn] = wire_count | 
|  | else: | 
|  | self.connections[conn] += wire_count | 
|  |  | 
|  | def _draw_connection(self, x0, y0, x1, y1, curve=False): | 
|  |  | 
|  | if curve: | 
|  | dx = x1 - x0 | 
|  | dy = y1 - y0 | 
|  |  | 
|  | cx = (x1 + x0) * 0.5 + dy * 0.33 | 
|  | cy = (y1 + y0) * 0.5 - dx * 0.33 | 
|  |  | 
|  | path = "M %.3f %.3f " % (x0, y0) | 
|  | path += "C %.3f %.3f %.3f %.3f %.3f %.3f" % \ | 
|  | (cx, cy, cx, cy, x1, y1) | 
|  |  | 
|  | self.svg.add(self.svg.path(d=path, fill="none", stroke="#000000")) | 
|  |  | 
|  | else: | 
|  | self.svg.add(self.svg.line((x0, y0), (x1, y1), stroke="#000000")) | 
|  |  | 
|  | def _grid_to_drawing(self, x, y): | 
|  |  | 
|  | xc = (x - self.grid_extent.xmin + 1) * self.BLOCK_SIZE | 
|  | yc = (y - self.grid_extent.ymin + 1) * self.BLOCK_SIZE | 
|  |  | 
|  | return xc, yc | 
|  |  | 
|  | def _create_drawing(self): | 
|  |  | 
|  | # Drawing size | 
|  | self.svg_dx = (self.grid_extent.xmax - self.grid_extent.xmin + 2) \ | 
|  | * self.BLOCK_SIZE | 
|  | self.svg_dy = (self.grid_extent.ymax - self.grid_extent.ymin + 2) \ | 
|  | * self.BLOCK_SIZE | 
|  |  | 
|  | # Create the drawing | 
|  | self.svg = svgwrite.Drawing( | 
|  | size=(self.svg_dx, self.svg_dy), profile="full", debug=False | 
|  | ) | 
|  |  | 
|  | def _get_tile_color(self, tile_name, tile): | 
|  | tile_type = tile["type"] | 
|  |  | 
|  | # Match | 
|  | if self.tile_colormap is not None: | 
|  | for rule in self.tile_colormap: | 
|  |  | 
|  | # Match by tile name | 
|  | if "name" in rule and re.match(rule["name"], tile_name): | 
|  | return rule["color"] | 
|  | # Match by tile type | 
|  | if "type" in rule and re.match(rule["type"], tile_type): | 
|  | return rule["color"] | 
|  |  | 
|  | # A default color | 
|  | return "#C0C0C0" | 
|  |  | 
|  | def _draw_grid(self): | 
|  |  | 
|  | svg_tiles = [] | 
|  | svg_text = [] | 
|  |  | 
|  | # Draw tiles | 
|  | print("Drawing grid...") | 
|  | for tile_name, tile in progressbar.progressbar(self.tilegrid.items()): | 
|  |  | 
|  | grid_x = tile["grid_x"] | 
|  | grid_y = tile["grid_y"] | 
|  | tile_type = tile["type"] | 
|  |  | 
|  | if self.grid_roi: | 
|  | if grid_x < self.grid_roi[0] or grid_x > self.grid_roi[2]: | 
|  | continue | 
|  | if grid_y < self.grid_roi[1] or grid_y > self.grid_roi[3]: | 
|  | continue | 
|  |  | 
|  | xc, yc = self._grid_to_drawing(grid_x, grid_y) | 
|  |  | 
|  | color = self._get_tile_color(tile_name, tile) | 
|  | if color is None: | 
|  | continue | 
|  |  | 
|  | font_size = self.BLOCK_RECT / 10 | 
|  |  | 
|  | # Rectangle | 
|  | svg_tiles.append( | 
|  | self.svg.rect( | 
|  | ( | 
|  | xc - self.BLOCK_RECT / 2, | 
|  | (self.svg_dy - 1 - yc) - self.BLOCK_RECT / 2 | 
|  | ), (self.BLOCK_RECT, self.BLOCK_RECT), | 
|  | stroke="#C0C0C0", | 
|  | fill=color | 
|  | ) | 
|  | ) | 
|  |  | 
|  | if grid_x & 1: | 
|  | text_ofs = -font_size | 
|  | else: | 
|  | text_ofs = font_size | 
|  |  | 
|  | # Tile name | 
|  | svg_text.append( | 
|  | self.svg.text( | 
|  | tile_name, ( | 
|  | xc - self.BLOCK_RECT / 2 + 2, | 
|  | (self.svg_dy - 1 - yc) - font_size / 2 + text_ofs | 
|  | ), | 
|  | font_size=font_size | 
|  | ) | 
|  | ) | 
|  | # Tile type | 
|  | svg_text.append( | 
|  | self.svg.text( | 
|  | tile_type, ( | 
|  | xc - self.BLOCK_RECT / 2 + 2, | 
|  | (self.svg_dy - 1 - yc) + font_size / 2 + text_ofs | 
|  | ), | 
|  | font_size=font_size | 
|  | ) | 
|  | ) | 
|  |  | 
|  | # Index | 
|  | svg_text.append( | 
|  | self.svg.text( | 
|  | "X%dY%d" % (grid_x, grid_y), ( | 
|  | xc - self.BLOCK_RECT / 2 + 2, | 
|  | (self.svg_dy - 1 - yc) + self.BLOCK_RECT / 2 - 2 | 
|  | ), | 
|  | font_size=font_size | 
|  | ) | 
|  | ) | 
|  |  | 
|  | # Add tiles to SVG | 
|  | for item in svg_tiles: | 
|  | self.svg.add(item) | 
|  |  | 
|  | # Add text to SVG | 
|  | for item in svg_text: | 
|  | self.svg.add(item) | 
|  |  | 
|  | def _draw_connections(self): | 
|  |  | 
|  | # Draw connections | 
|  | print("Drawing connections...") | 
|  | for conn, count in progressbar.progressbar(self.connections.items()): | 
|  |  | 
|  | if self.conn_roi: | 
|  |  | 
|  | if conn.loc0.x < self.grid_roi[0] or \ | 
|  | conn.loc0.x > self.grid_roi[2]: | 
|  | if conn.loc1.x < self.grid_roi[0] or \ | 
|  | conn.loc1.x > self.grid_roi[2]: | 
|  | continue | 
|  |  | 
|  | if conn.loc0.y < self.grid_roi[1] or \ | 
|  | conn.loc0.y > self.grid_roi[3]: | 
|  | if conn.loc1.y < self.grid_roi[1] or \ | 
|  | conn.loc1.y > self.grid_roi[3]: | 
|  | continue | 
|  |  | 
|  | dx = conn.loc1.x - conn.loc0.x | 
|  | dy = conn.loc1.y - conn.loc0.y | 
|  |  | 
|  | xc0, yc0 = self._grid_to_drawing(conn.loc0.x, conn.loc0.y) | 
|  | xc1, yc1 = self._grid_to_drawing(conn.loc1.x, conn.loc1.y) | 
|  |  | 
|  | max_count = int(self.BLOCK_RECT * 0.75 * 0.5) | 
|  | line_count = min(count, max_count) | 
|  |  | 
|  | # Mostly horizontal | 
|  | if abs(dx) > abs(dy): | 
|  |  | 
|  | for i in range(line_count): | 
|  | k = 0.5 if line_count == 1 else i / (line_count - 1) | 
|  | k = (k - 0.5) * 0.75 | 
|  |  | 
|  | if dx > 0: | 
|  | x0 = xc0 + self.BLOCK_RECT / 2 | 
|  | x1 = xc1 - self.BLOCK_RECT / 2 | 
|  | else: | 
|  | x0 = xc0 - self.BLOCK_RECT / 2 | 
|  | x1 = xc1 + self.BLOCK_RECT / 2 | 
|  |  | 
|  | y0 = yc0 + k * self.BLOCK_RECT | 
|  | y1 = yc1 + k * self.BLOCK_RECT | 
|  |  | 
|  | self._draw_connection( | 
|  | x0, (self.svg_dy - 1 - y0), x1, (self.svg_dy - 1 - y1), | 
|  | True | 
|  | ) | 
|  |  | 
|  | # Mostly vertical | 
|  | elif abs(dy) > abs(dx): | 
|  |  | 
|  | for i in range(line_count): | 
|  | k = 0.5 if line_count == 1 else i / (line_count - 1) | 
|  | k = (k - 0.5) * 0.75 | 
|  |  | 
|  | if dy > 0: | 
|  | y0 = yc0 + self.BLOCK_RECT / 2 | 
|  | y1 = yc1 - self.BLOCK_RECT / 2 | 
|  | else: | 
|  | y0 = yc0 - self.BLOCK_RECT / 2 | 
|  | y1 = yc1 + self.BLOCK_RECT / 2 | 
|  |  | 
|  | x0 = xc0 + k * self.BLOCK_RECT | 
|  | x1 = xc1 + k * self.BLOCK_RECT | 
|  |  | 
|  | self._draw_connection( | 
|  | x0, (self.svg_dy - 1 - y0), x1, (self.svg_dy - 1 - y1), | 
|  | True | 
|  | ) | 
|  |  | 
|  | # Diagonal | 
|  | else: | 
|  | # FIXME: Do it in a more elegant way... | 
|  |  | 
|  | max_count = int(max_count * 0.40) | 
|  | line_count = min(count, max_count) | 
|  |  | 
|  | for i in range(line_count): | 
|  | k = 0.5 if line_count == 1 else i / (line_count - 1) | 
|  | k = (k - 0.5) * 0.25 | 
|  |  | 
|  | if (dx > 0) ^ (dy > 0): | 
|  | x0 = xc0 + k * self.BLOCK_RECT | 
|  | x1 = xc1 + k * self.BLOCK_RECT | 
|  | y0 = yc0 + k * self.BLOCK_RECT | 
|  | y1 = yc1 + k * self.BLOCK_RECT | 
|  | else: | 
|  | x0 = xc0 - k * self.BLOCK_RECT | 
|  | x1 = xc1 - k * self.BLOCK_RECT | 
|  | y0 = yc0 + k * self.BLOCK_RECT | 
|  | y1 = yc1 + k * self.BLOCK_RECT | 
|  |  | 
|  | x0 += dx * self.BLOCK_RECT / 4 | 
|  | y0 += dy * self.BLOCK_RECT / 4 | 
|  | x1 -= dx * self.BLOCK_RECT / 4 | 
|  | y1 -= dy * self.BLOCK_RECT / 4 | 
|  |  | 
|  | self._draw_connection( | 
|  | x0, (self.svg_dy - 1 - y0), x1, (self.svg_dy - 1 - y1), | 
|  | False | 
|  | ) | 
|  |  | 
|  | def run(self): | 
|  |  | 
|  | if self.grid_roi is not None: | 
|  |  | 
|  | if self.conn_roi is None: | 
|  | self.conn_roi = self.grid_roi | 
|  | else: | 
|  | self.conn_roi[0] = max(self.conn_roi[0], self.grid_roi[0]) | 
|  | self.conn_roi[1] = max(self.conn_roi[1], self.grid_roi[1]) | 
|  | self.conn_roi[2] = min(self.conn_roi[2], self.grid_roi[2]) | 
|  | self.conn_roi[3] = min(self.conn_roi[3], self.grid_roi[3]) | 
|  |  | 
|  | self._create_drawing() | 
|  | self._draw_grid() | 
|  | self._draw_connections() | 
|  |  | 
|  | def save(self, file_name): | 
|  | self.svg.saveas(file_name) | 
|  |  | 
|  |  | 
|  | # ============================================================================= | 
|  |  | 
|  |  | 
|  | def main(): | 
|  |  | 
|  | # Parse arguments | 
|  | parser = argparse.ArgumentParser( | 
|  | description=__doc__, | 
|  | formatter_class=argparse.RawDescriptionHelpFormatter | 
|  | ) | 
|  |  | 
|  | parser.add_argument( | 
|  | "--tilegrid", | 
|  | type=str, | 
|  | default=None, | 
|  | help="Project X-Ray 'tilegrid.json' file" | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--tileconn", | 
|  | type=str, | 
|  | default=None, | 
|  | help="Project X-Ray 'tileconn.json' file" | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--arch-xml", | 
|  | type=str, | 
|  | default=None, | 
|  | help="Architecture definition XML file" | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--graph-xml", type=str, default=None, help="Routing graph XML file" | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--conn-db', | 
|  | type=str, | 
|  | default=None, | 
|  | help='Connection SQL database (eg. "channels.db")' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--db-table', | 
|  | type=str, | 
|  | default="tile", | 
|  | help='Table name in the SQL database to read (def. "tile")' | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--colormap", | 
|  | type=str, | 
|  | default=None, | 
|  | help="JSON file with tile coloring rules" | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--grid-roi", | 
|  | type=int, | 
|  | nargs=4, | 
|  | default=None, | 
|  | help="Grid ROI to draw (x0 y0 x1 y1)" | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--conn-roi", | 
|  | type=int, | 
|  | nargs=4, | 
|  | default=None, | 
|  | help="Connection ROI to draw (x0 y0 x1 y1)" | 
|  | ) | 
|  | parser.add_argument( | 
|  | "-o", type=str, default="layout.svg", help="Output SVG file name" | 
|  | ) | 
|  |  | 
|  | if len(sys.argv) <= 1: | 
|  | parser.print_help() | 
|  | exit(1) | 
|  |  | 
|  | args = parser.parse_args() | 
|  |  | 
|  | script_path = os.path.dirname(os.path.realpath(__file__)) | 
|  | if args.colormap is None: | 
|  | args.colormap = os.path.join(script_path, "tile_colormap.json") | 
|  |  | 
|  | # Create the visualizer | 
|  | visualizer = GridVisualizer() | 
|  |  | 
|  | # Set ROI | 
|  | visualizer.set_grid_roi(args.grid_roi) | 
|  | visualizer.set_conn_roi(args.conn_roi) | 
|  |  | 
|  | # Load arch XML file | 
|  | if args.arch_xml is not None: | 
|  | visualizer.load_tilegrid_from_arch_xml(args.arch_xml) | 
|  | # Load routing graph XML | 
|  | elif args.graph_xml is not None: | 
|  | visualizer.load_tilegrid_from_graph_xml(args.graph_xml) | 
|  | # Load JSON files | 
|  | elif args.tilegrid is not None: | 
|  |  | 
|  | visualizer.load_tilegrid_from_json(args.tilegrid) | 
|  |  | 
|  | if args.tileconn is not None: | 
|  | visualizer.load_tileconn_from_json(args.tileconn) | 
|  | # Load SQL database | 
|  | elif args.conn_db is not None: | 
|  | visualizer.load_tilegrid_from_conn_db(args.conn_db, args.db_table) | 
|  |  | 
|  | # No data input | 
|  | else: | 
|  | raise RuntimeError("No input data specified") | 
|  |  | 
|  | # Load tile colormap | 
|  | if args.colormap: | 
|  | visualizer.load_tile_colormap(args.colormap) | 
|  |  | 
|  | # Do the visualization | 
|  | visualizer.run() | 
|  |  | 
|  | # Save drawing | 
|  | if args.o.endswith(".svg"): | 
|  | print("Saving SVG...") | 
|  | visualizer.save(args.o) | 
|  |  | 
|  | elif args.o.endswith(".pdf"): | 
|  | print("Saving PDF...") | 
|  |  | 
|  | from cairosvg import svg2pdf | 
|  | svg2pdf(visualizer.svg.tostring(), write_to=args.o) | 
|  |  | 
|  | else: | 
|  | print("Unknown output file type '{}'".format(args.o)) | 
|  | exit(-1) | 
|  |  | 
|  | print("Done.") | 
|  |  | 
|  |  | 
|  | # ============================================================================= | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |