| """ Generates the top level VPR arch XML from the Project X-Ray database. |
| |
| By default this will generate a complete arch XML for all tile types specified. |
| |
| If the --use_roi flag is passed, only the tiles within the ROI will be included, |
| and synthetic IO pads will be created and connected to the routing fabric. |
| The mapping of the pad name to synthetic tile location will be outputted to the |
| file specified in the --synth_tiles output argument. This can be used to generate |
| IO placement spefications to target the synthetic IO pads. |
| |
| """ |
| from __future__ import print_function |
| import argparse |
| import prjxray.db |
| from prjxray.roi import Roi |
| from prjxray.overlay import Overlay |
| from prjxray import grid_types |
| import simplejson as json |
| import sys |
| |
| import lxml.etree as ET |
| |
| from prjxray_db_cache import DatabaseCache |
| from prjxray_tile_import import add_vpr_tile_prefix |
| |
| |
| def create_synth_io_tile( |
| complexblocklist_xml, tiles_xml, tile_name, num_input, num_output |
| ): |
| """ Creates synthetic IO pad tiles used to connect ROI inputs and outputs to the routing network. |
| """ |
| tile_xml = ET.SubElement(tiles_xml, 'tile', { |
| 'name': tile_name, |
| }) |
| |
| pad_name_out = 'outpad' |
| port_type_out = 'input' |
| |
| pad_name_in = 'inpad' |
| port_type_in = 'output' |
| |
| port_pin_in = '{}.{}'.format('SYN-INPAD', pad_name_in) |
| sub_port_pin_in = '{}.{}'.format('SYN_IN_SUB_TILE', pad_name_in) |
| |
| port_pin_out = '{}.{}'.format('SYN-OUTPAD', pad_name_out) |
| sub_port_pin_out = '{}.{}'.format('SYN_OUT_SUB_TILE', pad_name_out) |
| |
| if num_input != 0: |
| sub_tile_xml_in = ET.SubElement( |
| tile_xml, 'sub_tile', { |
| 'name': 'SYN_IN_SUB_TILE', |
| 'capacity': str(num_input) |
| } |
| ) |
| |
| ET.SubElement( |
| sub_tile_xml_in, 'fc', { |
| 'in_type': 'abs', |
| 'in_val': '2', |
| 'out_type': 'abs', |
| 'out_val': '2', |
| } |
| ) |
| |
| equivalent_sites_in = ET.SubElement( |
| sub_tile_xml_in, 'equivalent_sites' |
| ) |
| |
| site_in = ET.SubElement( |
| equivalent_sites_in, 'site', {'pb_type': 'SYN-INPAD'} |
| ) |
| |
| ET.SubElement( |
| sub_tile_xml_in, port_type_in, { |
| 'name': pad_name_in, |
| 'num_pins': '1', |
| } |
| ) |
| |
| ET.SubElement( |
| site_in, 'direct', { |
| 'from': sub_port_pin_in, |
| 'to': port_pin_in |
| } |
| ) |
| |
| if num_output != 0: |
| sub_tile_xml_out = ET.SubElement( |
| tile_xml, 'sub_tile', { |
| 'name': 'SYN_OUT_SUB_TILE', |
| 'capacity': str(num_output) |
| } |
| ) |
| |
| ET.SubElement( |
| sub_tile_xml_out, 'fc', { |
| 'in_type': 'abs', |
| 'in_val': '2', |
| 'out_type': 'abs', |
| 'out_val': '2', |
| } |
| ) |
| |
| equivalent_sites_out = ET.SubElement( |
| sub_tile_xml_out, 'equivalent_sites' |
| ) |
| |
| site_out = ET.SubElement( |
| equivalent_sites_out, 'site', {'pb_type': 'SYN-OUTPAD'} |
| ) |
| |
| ET.SubElement( |
| sub_tile_xml_out, port_type_out, { |
| 'name': pad_name_out, |
| 'num_pins': '1', |
| } |
| ) |
| |
| ET.SubElement( |
| site_out, 'direct', { |
| 'from': sub_port_pin_out, |
| 'to': port_pin_out |
| } |
| ) |
| |
| |
| def create_synth_pb_types(model_xml, complexblocklist_xml, is_overlay=False): |
| """ Creates synthetic IO pad tiles used to connect ROI inputs and outputs to the routing network. |
| """ |
| pb_xml_in = ET.SubElement( |
| complexblocklist_xml, 'pb_type', { |
| 'name': 'SYN-INPAD', |
| } |
| ) |
| |
| pb_xml_out = ET.SubElement( |
| complexblocklist_xml, 'pb_type', { |
| 'name': 'SYN-OUTPAD', |
| } |
| ) |
| |
| blif_model_out = '.output' |
| pad_name_out = 'outpad' |
| port_type_out = 'input' |
| |
| blif_model_in = '.input' |
| pad_name_in = 'inpad' |
| port_type_in = 'output' |
| |
| port_pin_in = '{}.{}'.format('SYN-INPAD', pad_name_in) |
| pad_pin_in = '{}.{}'.format(pad_name_in, pad_name_in) |
| |
| port_pin_out = '{}.{}'.format('SYN-OUTPAD', pad_name_out) |
| pad_pin_out = '{}.{}'.format(pad_name_out, pad_name_out) |
| |
| input_name_out = port_pin_out |
| output_name_out = pad_pin_out |
| |
| input_name_in = pad_pin_in |
| output_name_in = port_pin_in |
| |
| interconnect_xml_in = ET.SubElement(pb_xml_in, 'interconnect') |
| interconnect_xml_out = ET.SubElement(pb_xml_out, 'interconnect') |
| |
| ET.SubElement( |
| pb_xml_in, port_type_in, { |
| 'name': pad_name_in, |
| 'num_pins': '1', |
| } |
| ) |
| |
| ET.SubElement( |
| pb_xml_out, port_type_out, { |
| 'name': pad_name_out, |
| 'num_pins': '1', |
| } |
| ) |
| |
| if is_overlay: |
| # Add model for SYN_IBUF |
| ibuf_model = ET.SubElement(model_xml, 'model', {'name': 'SYN_IBUF'}) |
| |
| ibuf_input_ports = ET.SubElement(ibuf_model, 'input_ports', {}) |
| |
| ET.SubElement( |
| ibuf_input_ports, 'port', { |
| 'name': 'I', |
| 'combinational_sink_ports': 'O' |
| } |
| ) |
| |
| ibuf_output_ports = ET.SubElement(ibuf_model, 'output_ports', {}) |
| |
| ET.SubElement(ibuf_output_ports, 'port', { |
| 'name': 'O', |
| }) |
| |
| # Add model for SYN_OBUF |
| obuf_model = ET.SubElement(model_xml, 'model', {'name': 'SYN_OBUF'}) |
| |
| obuf_input_ports = ET.SubElement(obuf_model, 'input_ports', {}) |
| |
| ET.SubElement( |
| obuf_input_ports, 'port', { |
| 'name': 'I', |
| 'combinational_sink_ports': 'O' |
| } |
| ) |
| |
| obuf_output_ports = ET.SubElement(obuf_model, 'output_ports', {}) |
| |
| ET.SubElement(obuf_output_ports, 'port', { |
| 'name': 'O', |
| }) |
| |
| obuf_pb = ET.SubElement( |
| pb_xml_out, 'pb_type', { |
| 'name': "SYN-OBUF", |
| 'blif_model': '.subckt SYN_OBUF', |
| 'num_pb': '1', |
| } |
| ) |
| |
| ET.SubElement(obuf_pb, 'input', { |
| 'name': 'I', |
| 'num_pins': '1', |
| }) |
| |
| ET.SubElement(obuf_pb, 'output', { |
| 'name': 'O', |
| 'num_pins': '1', |
| }) |
| |
| ET.SubElement( |
| obuf_pb, 'delay_constant', { |
| 'max': '1e-11', |
| 'in_port': 'I', |
| 'out_port': 'O', |
| } |
| ) |
| |
| ibuf_pb = ET.SubElement( |
| pb_xml_in, 'pb_type', { |
| 'name': "SYN-IBUF", |
| 'blif_model': '.subckt SYN_IBUF', |
| 'num_pb': '1', |
| } |
| ) |
| |
| ET.SubElement(ibuf_pb, 'input', { |
| 'name': 'I', |
| 'num_pins': '1', |
| }) |
| |
| ET.SubElement(ibuf_pb, 'output', { |
| 'name': 'O', |
| 'num_pins': '1', |
| }) |
| |
| ET.SubElement( |
| ibuf_pb, 'delay_constant', { |
| 'max': '1e-11', |
| 'in_port': 'I', |
| 'out_port': 'O', |
| } |
| ) |
| pin_pb_type_in = ET.SubElement( |
| pb_xml_in, 'pb_type', { |
| 'name': pad_name_in, |
| 'blif_model': blif_model_in, |
| 'num_pb': '1', |
| } |
| ) |
| |
| pin_pb_type_out = ET.SubElement( |
| pb_xml_out, 'pb_type', { |
| 'name': pad_name_out, |
| 'blif_model': blif_model_out, |
| 'num_pb': '1', |
| } |
| ) |
| |
| ET.SubElement( |
| pin_pb_type_in, port_type_in, { |
| 'name': pad_name_in, |
| 'num_pins': '1', |
| } |
| ) |
| |
| ET.SubElement( |
| pin_pb_type_out, port_type_out, { |
| 'name': pad_name_out, |
| 'num_pins': '1', |
| } |
| ) |
| |
| if is_overlay: |
| direct_xml_out = ET.SubElement( |
| interconnect_xml_out, 'direct', { |
| 'name': '{}_to_{}'.format('SYN-OBUF.O', 'outpad.outpad'), |
| 'input': 'SYN-OBUF.O', |
| 'output': 'outpad.outpad', |
| } |
| ) |
| |
| ET.SubElement( |
| direct_xml_out, 'pack_pattern', { |
| 'in_port': 'SYN-OBUF.O', |
| 'name': '{}to{}'.format('OBUF', 'outpad'), |
| 'out_port': 'outpad.outpad', |
| } |
| ) |
| |
| ET.SubElement( |
| interconnect_xml_out, 'direct', { |
| 'name': '{}_to_{}'.format('SYN-OUTPAD.outpad', 'SYN-OBUF.I'), |
| 'input': 'SYN-OUTPAD.outpad', |
| 'output': 'SYN-OBUF.I', |
| } |
| ) |
| |
| direct_xml_in = ET.SubElement( |
| interconnect_xml_in, 'direct', { |
| 'name': '{}_to_{}'.format('inpad.inpad', 'SYN-IBUF.I'), |
| 'input': 'inpad.inpad', |
| 'output': 'SYN-IBUF.I', |
| } |
| ) |
| |
| ET.SubElement( |
| direct_xml_in, 'pack_pattern', { |
| 'in_port': 'inpad.inpad', |
| 'name': '{}to{}'.format('inpad', 'IBUF'), |
| 'out_port': 'SYN-IBUF.I', |
| } |
| ) |
| |
| ET.SubElement( |
| interconnect_xml_in, 'direct', { |
| 'name': '{}_to_{}'.format('SYN-IBUF.O', 'SYN-INPAD.inpad'), |
| 'input': 'SYN-IBUF.O', |
| 'output': 'SYN-INPAD.inpad', |
| } |
| ) |
| else: |
| direct_xml_out = ET.SubElement( |
| interconnect_xml_out, 'direct', { |
| 'name': '{}_to_{}'.format(input_name_out, output_name_out), |
| 'input': input_name_out, |
| 'output': output_name_out, |
| } |
| ) |
| |
| ET.SubElement( |
| direct_xml_out, 'delay_constant', { |
| 'max': '1e-11', |
| 'in_port': input_name_out, |
| 'out_port': output_name_out, |
| } |
| ) |
| |
| direct_xml_in = ET.SubElement( |
| interconnect_xml_in, 'direct', { |
| 'name': '{}_to_{}'.format(input_name_in, output_name_in), |
| 'input': input_name_in, |
| 'output': output_name_in, |
| } |
| ) |
| |
| ET.SubElement( |
| direct_xml_in, 'delay_constant', { |
| 'max': '1e-11', |
| 'in_port': input_name_in, |
| 'out_port': output_name_in, |
| } |
| ) |
| |
| |
| def create_synth_constant_tiles( |
| model_xml, complexblocklist_xml, tiles_xml, pb_name, signal |
| ): |
| """ Creates synthetic constant tile generates some constant signal. |
| |
| Routing import will create a global network to fan this signal to local |
| constant sources. |
| """ |
| pb_xml = ET.SubElement( |
| complexblocklist_xml, 'pb_type', { |
| 'name': pb_name, |
| } |
| ) |
| |
| tile_xml = ET.SubElement(tiles_xml, 'tile', { |
| 'name': pb_name, |
| }) |
| |
| sub_tile_xml = ET.SubElement(tile_xml, 'sub_tile', {'name': pb_name}) |
| |
| equivalent_sites = ET.SubElement(sub_tile_xml, 'equivalent_sites') |
| site = ET.SubElement(equivalent_sites, 'site', {'pb_type': pb_name}) |
| |
| ET.SubElement( |
| sub_tile_xml, 'fc', { |
| 'in_type': 'abs', |
| 'in_val': '2', |
| 'out_type': 'abs', |
| 'out_val': '2', |
| } |
| ) |
| |
| interconnect_xml = ET.SubElement(pb_xml, 'interconnect') |
| |
| blif_model = '.subckt ' + signal |
| port_type = 'output' |
| pin_name = signal |
| |
| ET.SubElement(pb_xml, port_type, { |
| 'name': pin_name, |
| 'num_pins': '1', |
| }) |
| |
| ET.SubElement( |
| sub_tile_xml, port_type, { |
| 'name': pin_name, |
| 'num_pins': '1', |
| } |
| ) |
| |
| port_pin = '{}.{}'.format(pb_name, pin_name) |
| pad_pin = '{}.{}'.format(pin_name, pin_name) |
| |
| ET.SubElement(site, 'direct', {'from': port_pin, 'to': port_pin}) |
| |
| input_name = pad_pin |
| output_name = port_pin |
| |
| pin_pb_type = ET.SubElement( |
| pb_xml, 'pb_type', { |
| 'name': pin_name, |
| 'blif_model': blif_model, |
| 'num_pb': '1', |
| } |
| ) |
| ET.SubElement( |
| pin_pb_type, port_type, { |
| 'name': pin_name, |
| 'num_pins': '1', |
| } |
| ) |
| |
| direct_xml = ET.SubElement( |
| interconnect_xml, 'direct', { |
| 'name': '{}_to_{}'.format(input_name, output_name), |
| 'input': input_name, |
| 'output': output_name, |
| } |
| ) |
| |
| ET.SubElement( |
| direct_xml, 'delay_constant', { |
| 'max': '1e-11', |
| 'in_port': input_name, |
| 'out_port': output_name, |
| } |
| ) |
| |
| model = ET.SubElement(model_xml, 'model', { |
| 'name': signal, |
| }) |
| |
| ET.SubElement(model, 'input_ports') |
| output_ports = ET.SubElement(model, 'output_ports') |
| ET.SubElement(output_ports, 'port', { |
| 'name': pin_name, |
| }) |
| |
| |
| def get_phy_tiles(conn, tile_pkey): |
| """ Returns the locations of all physical tiles for specified tile. """ |
| c = conn.cursor() |
| c2 = conn.cursor() |
| |
| phy_locs = [] |
| for (phy_tile_pkey, ) in c.execute(""" |
| SELECT phy_tile_pkey FROM tile_map WHERE tile_pkey = ?""", (tile_pkey, )): |
| c2.execute( |
| "SELECT grid_x, grid_y FROM phy_tile WHERE pkey = ?", |
| (phy_tile_pkey, ) |
| ) |
| loc = c2.fetchone() |
| phy_locs.append(grid_types.GridLoc(*loc)) |
| |
| return phy_locs |
| |
| |
| def is_in_roi(conn, roi, tile_pkey): |
| """ Returns if the specified tile is in the ROI. """ |
| phy_locs = get_phy_tiles(conn, tile_pkey) |
| return any(roi.tile_in_roi(loc) for loc in phy_locs) |
| |
| |
| # Map instance type (e.g. IOB_X1Y10) to: |
| # - Which coordinates are required (e.g. X, Y or X and Y) |
| # - Modulus on the coordinates |
| # |
| # For example, IO sites only need the Y coordinate, use a modulus of 2. |
| # So IOB_X1Y10 becomes IOB_Y0, IOB_X1Y11 becomes IOB_Y1, etc. |
| # Setting modulo to 0 results in omitting modulo operation. |
| # |
| # Sites which do not require prefix will contain a (None, None) pair. |
| PREFIX_REQUIRED = { |
| "IOB": ("Y", 2), |
| "IDELAY": ("Y", 2), |
| "ILOGIC": ("Y", 2), |
| "OLOGIC": ("Y", 2), |
| "BUFGCTRL": ("XY", (2, 16)), |
| "SLICEM": ("X", 2), |
| "SLICEL": ("X", 2), |
| "GTPE2_COMMON": (None, None), |
| "GTPE2_CHANNEL": (None, None), |
| "IBUFDS_GTE2": ("Y", 2), |
| "IPAD": (None, None), |
| "OPAD": (None, None), |
| } |
| |
| |
| def make_prefix(site, x, y, from_site_name=False): |
| """ Make tile FASM prefix for a given site. """ |
| if from_site_name: |
| site_type, _ = site.split('_') |
| else: |
| site_type = site |
| |
| prefix_required = PREFIX_REQUIRED[site_type] |
| |
| if prefix_required[0] == 'Y': |
| return site_type, '{}_Y{}'.format(site_type, y % prefix_required[1]) |
| elif prefix_required[0] == 'X': |
| return site_type, '{}_X{}'.format(site_type, x % prefix_required[1]) |
| elif prefix_required[0] == 'XY': |
| mod_x, mod_y = prefix_required[1] |
| return site_type, '{}_X{}Y{}'.format(site_type, x % mod_x, y % mod_y) |
| elif prefix_required[0] is None: |
| return site_type, None |
| else: |
| assert False, (site_type, prefix_required) |
| |
| |
| def get_site_prefixes(conn, tile_pkey): |
| cur = conn.cursor() |
| |
| site_prefixes = {} |
| cur.execute( |
| """ |
| WITH |
| sites_in_tile(site_pkey) AS ( |
| SELECT |
| DISTINCT wire_in_tile.site_pkey |
| FROM |
| wire_in_tile |
| INNER JOIN site ON site.pkey = wire_in_tile.site_pkey |
| WHERE |
| wire_in_tile.tile_type_pkey = (SELECT tile_type_pkey FROM tile WHERE pkey = ?) |
| AND |
| wire_in_tile.phy_tile_type_pkey IN (SELECT tile_type_pkey FROM phy_tile WHERE pkey IN |
| (SELECT phy_tile_pkey FROM tile_map WHERE tile_pkey = ?)) |
| AND |
| site_pin_pkey IS NOT NULL) |
| SELECT site_instance.name, site_instance.x_coord, site_instance.y_coord |
| FROM site_instance |
| WHERE |
| site_instance.site_pkey IN (SELECT site_pkey FROM sites_in_tile) |
| AND |
| site_instance.phy_tile_pkey IN (SELECT phy_tile_pkey FROM tile_map WHERE tile_pkey = ?); |
| """, (tile_pkey, tile_pkey, tile_pkey) |
| ) |
| for site_name, x, y in cur: |
| site_type, prefix = make_prefix(site_name, x, y, from_site_name=True) |
| assert site_type not in site_prefixes |
| site_prefixes[site_type] = prefix |
| |
| return site_prefixes |
| |
| |
| def create_capacity_prefix(c, tile_prefix, tile_pkey, tile_capacity): |
| """ Create FASM prefixes for all sites located within specified tile. |
| |
| This function should be invoke when the relevant tile has capacity > 1 |
| and therefore there should be prefixes for each instance. |
| |
| """ |
| c.execute( |
| """ |
| SELECT site_type.name, site_instance.name, site_instance.x_coord, site_instance.y_coord |
| FROM site_instance |
| INNER JOIN site ON site_instance.site_pkey = site.pkey |
| INNER JOIN site_type ON site.site_type_pkey = site_type.pkey |
| WHERE site_instance.phy_tile_pkey IN ( |
| SELECT |
| phy_tile_pkey |
| FROM |
| tile |
| WHERE |
| pkey = ? |
| ) |
| ORDER BY site_instance.x_coord, site_instance.y_coord, site_type.name;""", |
| (tile_pkey, ) |
| ) |
| |
| NO_SITE_TYPE_PREFIX = ["SLICEL", "SLICEM", "IBUFDS_GTE2"] |
| |
| prefixes = [] |
| |
| for site_type, site_name, x, y in sorted( |
| c, key=lambda site: (site[0], int(site[2]), int(site[3]))): |
| _, prefix = make_prefix(site_type, x, y) |
| |
| if prefix is None: |
| prefixes.append('{}.{}'.format(tile_prefix, site_type)) |
| else: |
| if site_type in NO_SITE_TYPE_PREFIX: |
| prefixes.append('{}.{}'.format(tile_prefix, prefix)) |
| else: |
| prefixes.append( |
| '{}.{}.{}'.format(tile_prefix, site_type, prefix) |
| ) |
| |
| assert len(prefixes |
| ) == tile_capacity, (tile_pkey, tile_capacity, len(prefixes)) |
| |
| return " ".join(prefixes) |
| |
| |
| def get_fasm_tile_prefix(conn, g, tile_pkey, site_as_tile_pkey, tile_capacity): |
| """ Returns FASM prefix of specified tile. """ |
| c = conn.cursor() |
| |
| c.execute( |
| """ |
| SELECT |
| phy_tile.name, |
| tile_type.name |
| FROM phy_tile |
| INNER JOIN tile_type |
| ON phy_tile.tile_type_pkey = tile_type.pkey |
| WHERE |
| phy_tile.pkey IN (SELECT phy_tile_pkey FROM tile_map WHERE tile_pkey = ?); |
| """, (tile_pkey, ) |
| ) |
| |
| # If this tile has multiples phy_tile's, make sure only one has bitstream |
| # data, otherwise the tile split was invalid. |
| tile_type_map = {} |
| for tilename, tile_type in c: |
| gridinfo = g.gridinfo_at_tilename(tilename) |
| is_vbrk = gridinfo.tile_type.find('VBRK') != -1 |
| is_pss = gridinfo.tile_type.startswith('PSS') |
| |
| # VBRK and PSS* tiles are known to have no bitstream data. |
| if not is_vbrk and not is_pss and not gridinfo.bits: |
| print( |
| '*** WARNING *** Tile {} appears to be missing bitstream data.' |
| .format(tilename), |
| file=sys.stderr |
| ) |
| |
| if gridinfo.bits: |
| # Each VPR tile can only have one prefix. |
| # If this assumption is violated, a more dramatic |
| # restructing is required. |
| tile_type_map[tile_type] = tilename |
| |
| if len(tile_type_map) > 1: |
| assert tile_capacity == 1 |
| tile_type_map.update(get_site_prefixes(conn, tile_pkey)) |
| return lambda single_xml: attach_multiple_prefixes_to_tile( |
| single_xml, tile_type_map |
| ) |
| else: |
| assert len(tile_type_map) == 1, tile_pkey |
| |
| tile_prefix = list(tile_type_map.values())[0] |
| |
| # If this tile is site_as_tile, add an additional prefix of the site |
| # that is embedded in the tile. |
| if site_as_tile_pkey is not None: |
| assert tile_capacity == 1 |
| c.execute( |
| "SELECT site_pkey FROM site_as_tile WHERE pkey = ?", |
| (site_as_tile_pkey, ) |
| ) |
| site_pkey = c.fetchone()[0] |
| |
| c.execute( |
| """ |
| SELECT site_type_pkey, x_coord FROM site WHERE pkey = ? |
| """, (site_pkey, ) |
| ) |
| site_type_pkey, x = c.fetchone() |
| |
| c.execute( |
| "SELECT name FROM site_type WHERE pkey = ?", |
| (site_type_pkey, ) |
| ) |
| site_type_name = c.fetchone()[0] |
| |
| tile_prefix = '{}.{}_X{}'.format(tile_prefix, site_type_name, x) |
| |
| if tile_capacity > 1: |
| tile_prefix = create_capacity_prefix( |
| c, tile_prefix, tile_pkey, tile_capacity |
| ) |
| |
| return lambda single_xml: attach_prefix_to_tile( |
| single_xml, tile_prefix |
| ) |
| |
| |
| def attach_prefix_to_tile(single_xml, fasm_tile_prefix): |
| meta = ET.SubElement(single_xml, 'metadata') |
| ET.SubElement( |
| meta, 'meta', { |
| 'name': 'fasm_prefix', |
| } |
| ).text = fasm_tile_prefix |
| |
| |
| # Map the following tile types to a more general name |
| TYPE_REMAP = { |
| "LIOI3_SING": "IOI3_TILE", |
| "LIOI3": "IOI3_TILE", |
| "LIOI3_TBYTESRC": "IOI3_TILE", |
| "LIOI3_TBYTETERM": "IOI3_TILE", |
| "RIOI3_SING": "IOI3_TILE", |
| "RIOI3": "IOI3_TILE", |
| "RIOI3_TBYTESRC": "IOI3_TILE", |
| "RIOI3_TBYTETERM": "IOI3_TILE", |
| "LIOB33": "IOB_TILE", |
| "LIOB33_SING": "IOB_TILE", |
| "RIOB33": "IOB_TILE", |
| "RIOB33_SING": "IOB_TILE", |
| } |
| |
| |
| def attach_multiple_prefixes_to_tile(single_xml, tile_type_map): |
| meta = ET.SubElement(single_xml, 'metadata') |
| ET.SubElement(meta, 'meta', { |
| 'name': 'fasm_placeholders', |
| }).text = '\n' + '\n'.join( |
| '{} : {}'.format(TYPE_REMAP.get(k, k), v) |
| for k, v in tile_type_map.items() |
| ) + '\n' |
| |
| |
| def get_tiles( |
| conn, g, roi, synth_loc_map, synth_tile_map, tile_types, tile_capacity |
| ): |
| """ Yields tiles in grid. |
| |
| Yields |
| ------ |
| vpr_tile_type : str |
| VPR tile type at this grid location. |
| grid_x, grid_y : int |
| Grid coordinate of tile |
| metadata_function : function that takes lxml.Element |
| Function for attaching metadata tags to <single> elements. |
| Function must be supplied, but doesn't need to add metadata if not |
| required. |
| |
| """ |
| c = conn.cursor() |
| c2 = conn.cursor() |
| |
| only_emit_roi = roi is not None |
| |
| for tile_pkey, grid_x, grid_y, phy_tile_pkey, tile_type_pkey, site_as_tile_pkey in c.execute( |
| """ |
| SELECT pkey, grid_x, grid_y, phy_tile_pkey, tile_type_pkey, site_as_tile_pkey FROM tile |
| """): |
| |
| if phy_tile_pkey is not None: |
| c2.execute( |
| "SELECT prohibited FROM site_instance WHERE phy_tile_pkey = ?", |
| (phy_tile_pkey, ) |
| ) |
| |
| any_prohibited_sites = any( |
| prohibited for (prohibited, ) in c2.fetchall() |
| ) |
| |
| # Skip generation of tiles containing prohibited sites |
| if any_prohibited_sites: |
| continue |
| |
| # Just output synth tiles, no additional processing is required here. |
| if (grid_x, grid_y) in synth_loc_map: |
| vpr_tile_type = synth_loc_map[(grid_x, grid_y)] |
| |
| yield vpr_tile_type, grid_x, grid_y, lambda x: None |
| continue |
| |
| c2.execute( |
| "SELECT name FROM tile_type WHERE pkey = ?", (tile_type_pkey, ) |
| ) |
| tile_type = c2.fetchone()[0] |
| if tile_type not in tile_types: |
| # We don't want this tile |
| continue |
| |
| if only_emit_roi and not is_in_roi(conn, roi, tile_pkey): |
| # Tile is outside ROI, skip it |
| continue |
| |
| vpr_tile_type = add_vpr_tile_prefix(tile_type) |
| |
| # For Zynq PSS* tiles do not emit fasm prefixes |
| if tile_type.startswith('PSS'): |
| |
| def get_none_tile_prefix(single_xml): |
| return None |
| |
| meta_fun = get_none_tile_prefix |
| else: |
| meta_fun = get_fasm_tile_prefix( |
| conn, g, tile_pkey, site_as_tile_pkey, tile_capacity[tile_type] |
| ) |
| |
| yield vpr_tile_type, grid_x, grid_y, meta_fun |
| |
| |
| def add_constant_synthetic_tiles(model_xml, complexblocklist_xml, tiles_xml): |
| synth_tile_types = {} |
| create_synth_constant_tiles( |
| model_xml, complexblocklist_xml, tiles_xml, 'SYN-VCC', 'VCC' |
| ) |
| create_synth_constant_tiles( |
| model_xml, complexblocklist_xml, tiles_xml, 'SYN-GND', 'GND' |
| ) |
| |
| synth_tile_types['VCC'] = 'SYN-VCC' |
| synth_tile_types['GND'] = 'SYN-GND' |
| |
| return synth_tile_types |
| |
| |
| def add_direct(directlist_xml, direct): |
| direct_dict = { |
| 'name': |
| '{}_to_{}_dx_{}_dy_{}_dz_{}'.format( |
| direct['from_pin'], direct['to_pin'], direct['x_offset'], |
| direct['y_offset'], direct['z_offset'] |
| ), |
| 'from_pin': |
| add_vpr_tile_prefix(direct['from_pin']), |
| 'to_pin': |
| add_vpr_tile_prefix(direct['to_pin']), |
| 'x_offset': |
| str(direct['x_offset']), |
| 'y_offset': |
| str(direct['y_offset']), |
| 'z_offset': |
| str(direct['z_offset']), |
| } |
| |
| # If the switch is a delayless_switch, the switch name |
| # needs to be avoided as VPR automatically assigns |
| # the delayless switch to this direct connection |
| if direct['switch_name'] != '__vpr_delayless_switch__': |
| direct_dict['switch_name'] = direct['switch_name'] |
| |
| ET.SubElement(directlist_xml, 'direct', direct_dict) |
| |
| |
| def insert_constant_tiles(conn, model_xml, complexblocklist_xml, tiles_xml): |
| c = conn.cursor() |
| |
| # Always add 'GND' and 'VCC' synth tiles |
| synth_tile_map = add_constant_synthetic_tiles( |
| model_xml, complexblocklist_xml, tiles_xml |
| ) |
| synth_loc_map = {} |
| |
| c.execute('SELECT pkey FROM tile_type WHERE name = "NULL";') |
| null_tile_type_pkey = c.fetchone()[0] |
| |
| # Get all 'NULL' tile locations |
| c.execute( |
| """ |
| SELECT grid_x, grid_y |
| FROM phy_tile |
| WHERE tile_type_pkey = ? |
| ORDER BY grid_y, grid_x ASC |
| LIMIT 2 |
| """, (null_tile_type_pkey, ) |
| ) |
| |
| locs = list(c.fetchall()) |
| assert len(locs) >= 2 |
| |
| loc = {'VCC': locs[0], 'GND': locs[1]} |
| |
| c.execute( |
| """ |
| SELECT pkey, tile_type_pkey FROM phy_tile |
| WHERE grid_x = ? AND grid_y = ?""", loc['VCC'] |
| ) |
| vcc_phy_tile_pkey, vcc_tile_type_pkey = c.fetchone() |
| assert vcc_tile_type_pkey == null_tile_type_pkey, vcc_tile_type_pkey |
| |
| c.execute( |
| """ |
| SELECT pkey, grid_x, grid_y FROM tile WHERE phy_tile_pkey = ? |
| """, (vcc_phy_tile_pkey, ) |
| ) |
| results = c.fetchall() |
| assert len(results) == 1, results |
| _, vcc_grid_x, vcc_grid_y = results[0] |
| synth_loc_map[(vcc_grid_x, vcc_grid_y)] = synth_tile_map['VCC'] |
| |
| c.execute( |
| """ |
| SELECT pkey, tile_type_pkey FROM phy_tile |
| WHERE grid_x = ? AND grid_y = ?""", loc['GND'] |
| ) |
| gnd_phy_tile_pkey, gnd_tile_type_pkey = c.fetchone() |
| assert gnd_tile_type_pkey == null_tile_type_pkey |
| |
| c.execute( |
| """ |
| SELECT pkey, grid_x, grid_y FROM tile WHERE phy_tile_pkey = ? |
| """, (gnd_phy_tile_pkey, ) |
| ) |
| results = c.fetchall() |
| assert len(results) == 1, results |
| _, gnd_grid_x, gnd_grid_y = results[0] |
| synth_loc_map[(gnd_grid_x, gnd_grid_y)] = synth_tile_map['GND'] |
| |
| return synth_tile_map, synth_loc_map |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser(description="Generate arch.xml") |
| parser.add_argument( |
| '--db_root', required=True, help="Project X-Ray database to use." |
| ) |
| parser.add_argument('--part', required=True, help="FPGA part") |
| parser.add_argument( |
| '--output-arch', |
| nargs='?', |
| type=argparse.FileType('w'), |
| help="""File to output arch.""" |
| ) |
| parser.add_argument( |
| '--tile-types', required=True, help="Semi-colon seperated tile types." |
| ) |
| parser.add_argument( |
| '--pb_types', |
| required=True, |
| help="Semi-colon seperated pb_types types." |
| ) |
| parser.add_argument( |
| '--pin_assignments', required=True, type=argparse.FileType('r') |
| ) |
| parser.add_argument('--use_roi', required=False) |
| parser.add_argument('--use_overlay', required=False) |
| parser.add_argument('--device', required=True) |
| parser.add_argument('--synth_tiles', required=False) |
| parser.add_argument('--connection_database', required=True) |
| parser.add_argument( |
| '--graph_limit', |
| help='Limit grid to specified dimensions in x_min,y_min,x_max,y_max', |
| ) |
| |
| args = parser.parse_args() |
| |
| tile_types = args.tile_types.split(',') |
| pb_types = args.pb_types.split(',') |
| |
| model_xml_spec = "../../tiles/{0}/{0}.model.xml" |
| pbtype_xml_spec = "../../tiles/{0}/{0}.pb_type.xml" |
| tile_xml_spec = "../../tiles/{0}/{0}.tile.xml" |
| |
| xi_url = "http://www.w3.org/2001/XInclude" |
| ET.register_namespace('xi', xi_url) |
| xi_include = "{%s}include" % xi_url |
| |
| arch_xml = ET.Element( |
| 'architecture', |
| {}, |
| nsmap={'xi': xi_url}, |
| ) |
| |
| model_xml = ET.SubElement(arch_xml, 'models') |
| for pb_type in pb_types: |
| ET.SubElement( |
| model_xml, xi_include, { |
| 'href': model_xml_spec.format(pb_type.lower()), |
| 'xpointer': "xpointer(models/child::node())", |
| } |
| ) |
| |
| tiles_xml = ET.SubElement(arch_xml, 'tiles') |
| tile_capacity = {} |
| for tile_type in tile_types: |
| uri = tile_xml_spec.format(tile_type.lower()) |
| ET.SubElement(tiles_xml, xi_include, { |
| 'href': uri, |
| }) |
| |
| with open(uri) as f: |
| tile_xml = ET.parse(f, ET.XMLParser()) |
| |
| tile_root = tile_xml.getroot() |
| assert tile_root.tag == 'tile' |
| tile_capacity[tile_type] = 0 |
| |
| for sub_tile in tile_root.iter('sub_tile'): |
| if 'capacity' in sub_tile.attrib: |
| tile_capacity[tile_type] += int( |
| sub_tile.attrib['capacity'] |
| ) |
| else: |
| tile_capacity[tile_type] += 1 |
| |
| complexblocklist_xml = ET.SubElement(arch_xml, 'complexblocklist') |
| for pb_type in pb_types: |
| ET.SubElement( |
| complexblocklist_xml, xi_include, { |
| 'href': pbtype_xml_spec.format(pb_type.lower()), |
| } |
| ) |
| |
| layout_xml = ET.SubElement(arch_xml, 'layout') |
| db = prjxray.db.Database(args.db_root, args.part) |
| g = db.grid() |
| |
| synth_tiles = {} |
| synth_tiles['tiles'] = {} |
| synth_loc_map = {} |
| synth_tile_map = {} |
| roi = None |
| if args.use_roi: |
| with open(args.use_roi) as f: |
| j = json.load(f) |
| |
| with open(args.synth_tiles) as f: |
| synth_tiles = json.load(f) |
| |
| roi = Roi( |
| db=db, |
| x1=j['info']['GRID_X_MIN'], |
| y1=j['info']['GRID_Y_MIN'], |
| x2=j['info']['GRID_X_MAX'], |
| y2=j['info']['GRID_Y_MAX'], |
| ) |
| |
| for _, tile_info in synth_tiles['tiles'].items(): |
| if tile_info['pins'][0]['port_type'] in ['GND', 'VCC']: |
| continue |
| |
| assert tuple(tile_info['loc']) not in synth_loc_map |
| tile_name = tile_info['tile_name'] |
| num_input = len( |
| list( |
| filter( |
| lambda t: t['port_type'] == 'output', tile_info['pins'] |
| ) |
| ) |
| ) |
| num_output = len( |
| list( |
| filter( |
| lambda t: t['port_type'] == 'input', tile_info['pins'] |
| ) |
| ) |
| ) |
| |
| create_synth_io_tile( |
| complexblocklist_xml, tiles_xml, tile_name, num_input, |
| num_output |
| ) |
| |
| synth_loc_map[tuple(tile_info['loc'])] = tile_name |
| |
| create_synth_pb_types(model_xml, complexblocklist_xml) |
| |
| synth_tile_map = add_constant_synthetic_tiles( |
| model_xml, complexblocklist_xml, tiles_xml |
| ) |
| |
| for _, tile_info in synth_tiles['tiles'].items(): |
| if tile_info['pins'][0]['port_type'] not in ['GND', 'VCC']: |
| continue |
| |
| assert tuple(tile_info['loc']) not in synth_loc_map |
| |
| vpr_tile_type = synth_tile_map[tile_info['pins'][0]['port_type']] |
| synth_loc_map[tuple(tile_info['loc'])] = vpr_tile_type |
| |
| elif args.graph_limit: |
| x_min, y_min, x_max, y_max = map(int, args.graph_limit.split(',')) |
| roi = Roi( |
| db=db, |
| x1=x_min, |
| y1=y_min, |
| x2=x_max, |
| y2=y_max, |
| ) |
| elif args.use_overlay: |
| with open(args.use_overlay) as f: |
| j = json.load(f) |
| |
| with open(args.synth_tiles) as f: |
| synth_tiles = json.load(f) |
| |
| region_dict = dict() |
| for r in synth_tiles['info']: |
| bounds = ( |
| r['GRID_X_MIN'], r['GRID_X_MAX'], r['GRID_Y_MIN'], |
| r['GRID_Y_MAX'] |
| ) |
| region_dict[r['name']] = bounds |
| |
| roi = Overlay(region_dict=region_dict) |
| |
| for _, tile_info in synth_tiles['tiles'].items(): |
| if tile_info['pins'][0]['port_type'] in ['GND', 'VCC']: |
| continue |
| |
| assert tuple(tile_info['loc']) not in synth_loc_map |
| tile_name = tile_info['tile_name'] |
| num_input = len( |
| list( |
| filter( |
| lambda t: t['port_type'] == 'output', tile_info['pins'] |
| ) |
| ) |
| ) |
| num_output = len( |
| list( |
| filter( |
| lambda t: t['port_type'] == 'input', tile_info['pins'] |
| ) |
| ) |
| ) |
| |
| create_synth_io_tile( |
| complexblocklist_xml, tiles_xml, tile_name, num_input, |
| num_output |
| ) |
| |
| synth_loc_map[tuple(tile_info['loc'])] = tile_name |
| |
| create_synth_pb_types(model_xml, complexblocklist_xml, True) |
| |
| with DatabaseCache(args.connection_database, read_only=True) as conn: |
| c = conn.cursor() |
| |
| if 'GND' not in synth_tile_map: |
| synth_tile_map, synth_loc_map_const = insert_constant_tiles( |
| conn, model_xml, complexblocklist_xml, tiles_xml |
| ) |
| |
| synth_loc_map.update(synth_loc_map_const) |
| |
| # Find the grid extent. |
| y_max = 0 |
| x_max = 0 |
| for grid_x, grid_y in c.execute("SELECT grid_x, grid_y FROM tile"): |
| x_max = max(grid_x + 2, x_max) |
| y_max = max(grid_y + 2, y_max) |
| |
| name = '{}-test'.format(args.device) |
| fixed_layout_xml = ET.SubElement( |
| layout_xml, 'fixed_layout', { |
| 'name': name, |
| 'height': str(y_max), |
| 'width': str(x_max), |
| } |
| ) |
| |
| for vpr_tile_type, grid_x, grid_y, metadata_function in get_tiles( |
| conn=conn, |
| g=g, |
| roi=roi, |
| synth_loc_map=synth_loc_map, |
| synth_tile_map=synth_tile_map, |
| tile_types=tile_types, |
| tile_capacity=tile_capacity, |
| ): |
| single_xml = ET.SubElement( |
| fixed_layout_xml, 'single', { |
| 'priority': '1', |
| 'type': vpr_tile_type, |
| 'x': str(grid_x), |
| 'y': str(grid_y), |
| } |
| ) |
| metadata_function(single_xml) |
| |
| switchlist_xml = ET.SubElement(arch_xml, 'switchlist') |
| |
| for name, internal_capacitance, drive_resistance, intrinsic_delay, \ |
| switch_type in c.execute(""" |
| SELECT |
| name, |
| internal_capacitance, |
| drive_resistance, |
| intrinsic_delay, |
| switch_type |
| FROM |
| switch |
| WHERE |
| name != "__vpr_delayless_switch__";"""): |
| |
| attrib = { |
| 'type': switch_type, |
| 'name': name, |
| "R": str(drive_resistance), |
| "Cin": str(0), |
| "Cout": str(0), |
| "Tdel": str(intrinsic_delay), |
| } |
| |
| if internal_capacitance != 0: |
| attrib["Cinternal"] = str(internal_capacitance) |
| |
| if False: |
| attrib["mux_trans_size"] = str(0) |
| attrib["buf_size"] = str(0) |
| |
| ET.SubElement(switchlist_xml, 'switch', attrib) |
| |
| segmentlist_xml = ET.SubElement(arch_xml, 'segmentlist') |
| |
| # VPR requires a segment, so add one. |
| dummy_xml = ET.SubElement( |
| segmentlist_xml, 'segment', { |
| 'name': 'dummy', |
| 'length': '2', |
| 'freq': '1.0', |
| 'type': 'bidir', |
| 'Rmetal': '0', |
| 'Cmetal': '0', |
| } |
| ) |
| ET.SubElement(dummy_xml, 'wire_switch', { |
| 'name': 'buffer', |
| }) |
| ET.SubElement(dummy_xml, 'opin_switch', { |
| 'name': 'buffer', |
| }) |
| ET.SubElement(dummy_xml, 'sb', { |
| 'type': 'pattern', |
| }).text = ' '.join('1' for _ in range(3)) |
| ET.SubElement(dummy_xml, 'cb', { |
| 'type': 'pattern', |
| }).text = ' '.join('1' for _ in range(2)) |
| |
| for (name, length) in c.execute("SELECT name, length FROM segment"): |
| if length is None: |
| length = 1 |
| |
| segment_xml = ET.SubElement( |
| segmentlist_xml, 'segment', { |
| 'name': name, |
| 'length': str(length), |
| 'freq': '1.0', |
| 'type': 'bidir', |
| 'Rmetal': '0', |
| 'Cmetal': '0', |
| } |
| ) |
| ET.SubElement(segment_xml, 'wire_switch', { |
| 'name': 'buffer', |
| }) |
| ET.SubElement(segment_xml, 'opin_switch', { |
| 'name': 'buffer', |
| }) |
| ET.SubElement(segment_xml, 'sb', { |
| 'type': 'pattern', |
| }).text = ' '.join('1' for _ in range(length + 1)) |
| ET.SubElement(segment_xml, 'cb', { |
| 'type': 'pattern', |
| }).text = ' '.join('1' for _ in range(length)) |
| |
| ET.SubElement( |
| switchlist_xml, |
| 'switch', |
| { |
| 'type': 'mux', |
| 'name': 'buffer', |
| "R": "551", |
| "Cin": ".77e-15", |
| "Cout": "4e-15", |
| # TODO: This value should be the "typical" pip switch delay from |
| # This value is the dominate term in the inter-cluster delay |
| # estimate. |
| "Tdel": "0.178e-9", |
| "mux_trans_size": "2.630740", |
| "buf_size": "27.645901" |
| } |
| ) |
| |
| device_xml = ET.SubElement(arch_xml, 'device') |
| |
| ET.SubElement( |
| device_xml, 'sizing', { |
| "R_minW_nmos": "6065.520020", |
| "R_minW_pmos": "18138.500000", |
| } |
| ) |
| ET.SubElement(device_xml, 'area', { |
| "grid_logic_tile_area": "14813.392", |
| }) |
| ET.SubElement( |
| device_xml, 'connection_block', { |
| "input_switch_name": "buffer", |
| } |
| ) |
| ET.SubElement(device_xml, 'switch_block', { |
| "type": "wilton", |
| "fs": "3", |
| }) |
| chan_width_distr_xml = ET.SubElement(device_xml, 'chan_width_distr') |
| |
| ET.SubElement( |
| chan_width_distr_xml, 'x', { |
| 'distr': 'uniform', |
| 'peak': '1.0', |
| } |
| ) |
| ET.SubElement( |
| chan_width_distr_xml, 'y', { |
| 'distr': 'uniform', |
| 'peak': '1.0', |
| } |
| ) |
| |
| directlist_xml = ET.SubElement(arch_xml, 'directlist') |
| |
| pin_assignments = json.load(args.pin_assignments) |
| |
| # Choose smallest distance for block to block connections with multiple |
| # direct_connections. VPR cannot handle multiple block to block connections. |
| directs = {} |
| for direct in pin_assignments['direct_connections']: |
| key = (direct['from_pin'], direct['to_pin']) |
| |
| if key not in directs: |
| directs[key] = [] |
| |
| directs[key].append( |
| (abs(direct['x_offset']) + abs(direct['y_offset']), direct) |
| ) |
| |
| ALLOWED_ZERO_OFFSET_DIRECT = [ |
| "GTP_CHANNEL_0", |
| "GTP_CHANNEL_1", |
| "GTP_CHANNEL_2", |
| "GTP_CHANNEL_3", |
| "GTP_CHANNEL_0_MID_LEFT", |
| "GTP_CHANNEL_1_MID_LEFT", |
| "GTP_CHANNEL_2_MID_LEFT", |
| "GTP_CHANNEL_3_MID_LEFT", |
| "GTP_CHANNEL_0_MID_RIGHT", |
| "GTP_CHANNEL_1_MID_RIGHT", |
| "GTP_CHANNEL_2_MID_RIGHT", |
| "GTP_CHANNEL_3_MID_RIGHT", |
| "GTP_COMMON_MID_LEFT", |
| "GTP_COMMON_MID_RIGHT", |
| ] |
| |
| zero_offset_directs = dict() |
| |
| for direct in directs.values(): |
| _, direct = min(direct, key=lambda v: v[0]) |
| from_tile = direct['from_pin'].split('.')[0] |
| to_tile = direct['to_pin'].split('.')[0] |
| |
| if from_tile not in tile_types: |
| continue |
| if to_tile not in tile_types: |
| continue |
| |
| # In general, the Z offset is 0, except for special cases |
| # such as for the GTP tiles, where there are direct connections |
| # within the same (x, y) cooredinates, but between different sub_tiles |
| direct['z_offset'] = 0 |
| |
| if direct['x_offset'] == 0 and direct['y_offset'] == 0: |
| if from_tile == to_tile and from_tile in ALLOWED_ZERO_OFFSET_DIRECT: |
| if from_tile not in zero_offset_directs: |
| zero_offset_directs[from_tile] = list() |
| |
| zero_offset_directs[from_tile].append(direct) |
| |
| continue |
| |
| add_direct(directlist_xml, direct) |
| |
| for tile, directs in zero_offset_directs.items(): |
| uri = tile_xml_spec.format(tile.lower()) |
| ports = list() |
| |
| with open(uri) as f: |
| tile_xml = ET.parse(f, ET.XMLParser()) |
| |
| tile_root = tile_xml.getroot() |
| |
| for capacity, sub_tile in enumerate(tile_root.iter('sub_tile')): |
| for in_port in sub_tile.iter('input'): |
| ports.append((in_port.attrib["name"], capacity)) |
| for out_port in sub_tile.iter('output'): |
| ports.append((out_port.attrib["name"], capacity)) |
| for clk_port in sub_tile.iter('clock'): |
| ports.append((clk_port.attrib["name"], capacity)) |
| |
| for direct in directs: |
| tile_type, from_port = direct['from_pin'].split('.') |
| _, to_port = direct['to_pin'].split('.') |
| |
| if tile != tile_type: |
| continue |
| |
| from_port_capacity = None |
| to_port_capacity = None |
| for port, capacity in ports: |
| if port == from_port: |
| from_port_capacity = capacity |
| if port == to_port: |
| to_port_capacity = capacity |
| |
| assert from_port_capacity is not None and to_port_capacity is not None, ( |
| tile, from_port, to_port |
| ) |
| direct["z_offset"] = to_port_capacity - from_port_capacity |
| |
| add_direct(directlist_xml, direct) |
| |
| arch_xml_str = ET.tostring(arch_xml, pretty_print=True).decode('utf-8') |
| args.output_arch.write(arch_xml_str) |
| args.output_arch.close() |
| |
| |
| if __name__ == '__main__': |
| main() |