text: #!/usr/bin/env python
  
  import argparse
  import sys
  import copy
  from collections import OrderedDict
  
  from lxml import etree as ET
  
  class SequentialPort:
      def __init__(self, port, clock):
          self.port = port
          self.clock = clock
  
  class CombEdge:
      def __init__(self, src_port, sink_port):
          self.src_port = src_port
          self.sink_ports = sink_port
  
  class ModelTiming:
      def __init__(self):
          self.sequential_ports = set()
          self.comb_edges = {}
  
  INDENT = "    "
  
  #To add support for new upgrades/features add the identifier string here
  #and added a guarded call in main()
  supported_upgrades = [
      "add_model_timing",
      "upgrade_fc_overrides",
      "upgrade_device_layout",
      "remove_io_chan_distr",
      "upgrade_pinlocations",
      "uniqify_interconnect_names",
      "upgrade_connection_block_input_switch",
      "upgrade_switch_types",
      "rename_fc_attributes",
      "longline_no_sb_cb",
      "upgrade_port_equivalence",
      "upgrade_complex_sb_num_conns",
      "add_missing_comb_model_internal_timing_edges",
      "add_tile_tags",
  ]
  
  def parse_args():
      parser = argparse.ArgumentParser(description="Upgrade Legacy VTR architecture files")
  
      parser.add_argument("xml_file", help="xml_file to update (modified in place)")
      parser.add_argument("--features",
                          nargs="*",
                          help="List of features/upgrades to apply (default: %(default)s)",
                          choices=supported_upgrades,
                          default=supported_upgrades)
      parser.add_argument("--debug", default=False, action="store_true", help="Print to stdout instead of modifying file inplace")
      parser.add_argument("--pretty", default=True, help="Pretty print the output? (default: %(default)s)")
  
      return parser.parse_args()
  
  def main():
      args = parse_args()
  
      print args.xml_file
      parser = ET.XMLParser(remove_blank_text=True)
      root = ET.parse(args.xml_file, parser)
  
      root_tags = root.findall(".")
      assert len(root_tags) == 1
      arch = root_tags[0]
  
      if arch.tag != "architecture":
          print "Warning! Not an architecture file, exiting..."
          sys.exit(0)
  
      modified = False
      if "add_model_timing" in args.features:
          result = add_model_timing(arch)
          if result:
              modified = True
  
      if "upgrade_fc_overrides" in args.features:
          result = upgrade_fc_overrides(arch)
          if result:
              modified = True
  
      if "upgrade_device_layout" in args.features:
          result = upgrade_device_layout(arch)
          if result:
              modified = True
  
      if "remove_io_chan_distr" in args.features:
          result = remove_io_chan_distr(arch)
          if result:
              modified = True
  
      if "upgrade_pinlocations" in args.features:
          result = upgrade_pinlocations(arch)
          if result:
              modified = True
  
      if "uniqify_interconnect_names" in args.features:
          result = uniqify_interconnect_names(arch)
          if result:
              modified = True
  
      if "upgrade_connection_block_input_switch" in args.features:
          result = upgrade_connection_block_input_switch(arch)
          if result:
              modified = True
  
      if "upgrade_switch_types" in args.features:
          result = upgrade_switch_types(arch)
          if result:
              modified = True
  
      if "rename_fc_attributes" in args.features:
          result = rename_fc_attributes(arch)
          if result:
              modified = True
  
      if "longline_no_sb_cb" in args.features:
          result = remove_longline_sb_cb(arch)
          if result:
              modified = True
  
      if "upgrade_port_equivalence" in args.features:
          result = upgrade_port_equivalence(arch)
          if result:
              modified = True
  
      if "upgrade_complex_sb_num_conns" in args.features:
          result = upgrade_complex_sb_num_conns(arch)
          if result:
              modified = True
  
  
      if "add_missing_comb_model_internal_timing_edges" in args.features:
          result = add_missing_comb_model_internal_timing_edges(arch)
          if result:
              modified = True
  
      if "add_tile_tags" in args.features:
          result = add_tile_tags(arch)
          if result:
              modified = True
  
      if modified:
          if args.debug:
              root.write(sys.stdout, pretty_print=args.pretty)
          else:
              with open(args.xml_file, "w") as f:
                  root.write(f, pretty_print=args.pretty)
  
  def add_model_timing(arch):
      """
      Records the timing edges specified via timing annotationson primitive PB types,
      and adds the appropriate timing edges to the primitive descriptions in the models
      section.
      """
      models = arch.findall("./models/model")
  
      #Find all primitive pb types
      prim_pbs = arch.findall(".//pb_type[@blif_model]")
  
      #Build up the timing specifications from
      default_models = frozenset([".input", ".output", ".latch", ".names"])
      primitive_timing_specs = {}
      for prim_pb in prim_pbs:
  
          blif_model = prim_pb.attrib['blif_model']
  
          if blif_model in default_models:
              continue
  
          assert blif_model.startswith(".subckt ")
          blif_model = blif_model[len(".subckt "):]
  
          if blif_model not in primitive_timing_specs:
              primitive_timing_specs[blif_model] = ModelTiming()
  
          #Find combinational edges
          for xpath_pattern in ["./delay_constant", "./delay_matrix"]:
              for delay_const in prim_pb.findall(xpath_pattern):
                  iports = get_port_names(delay_const.attrib['in_port'])
                  oports = get_port_names(delay_const.attrib['out_port'])
  
                  for iport in iports:
                      if iport not in primitive_timing_specs[blif_model].comb_edges:
                          primitive_timing_specs[blif_model].comb_edges[iport] = set()
  
                      for oport in oports:
                          primitive_timing_specs[blif_model].comb_edges[iport].add(oport)
  
          #Find sequential ports
          for xpath_pattern in ["./T_setup", "./T_clock_to_Q", "./T_hold"]:
              for seq_tag in prim_pb.findall(xpath_pattern):
                  ports = get_port_names(seq_tag.attrib['port'])
                  for port in ports:
                      clk = seq_tag.attrib['clock']
  
                      primitive_timing_specs[blif_model].sequential_ports.add((port, clk))
  
      changed = False
      for model in models:
          if model.attrib['name'] not in primitive_timing_specs:
              print "Warning: no timing specifications found for {}".format(mode.attrib['name'])
              continue
  
          model_timing = primitive_timing_specs[model.attrib['name']]
  
          #Mark model ports as sequential
          for port_name, clock_name in model_timing.sequential_ports:
              port = model.find(".//port[@name='{}']".format(port_name))
              port.attrib['clock'] = clock_name
              changed = True
  
          #Mark combinational edges from sources to sink ports
          for src_port, sink_ports in model_timing.comb_edges.iteritems():
              xpath_pattern = "./input_ports/port[@name='{}']".format(src_port)
              port = model.find(xpath_pattern)
              port.attrib['combinational_sink_ports'] = ' '.join(sink_ports)
              changed = True
  
      return changed
  
  def upgrade_fc_overrides(arch):
      """
      Convets the legacy block <fc> pin and segment override specifications,
      to the new unified format.
      """
      fc_tags = arch.findall(".//fc")
  
      changed = False
      for fc_tag in fc_tags:
          assert fc_tag.tag == "fc"
  
          old_fc_pin_overrides = fc_tag.findall("./pin")
          old_fc_seg_overrides = fc_tag.findall("./segment")
  
          assert len(old_fc_pin_overrides) == 0 or len(old_fc_seg_overrides) == 0, "Can only have pin or seg overrides (not both)"
  
          for old_pin_override in old_fc_pin_overrides:
  
              port = old_pin_override.attrib['name']
              fc_type = old_pin_override.attrib['fc_type']
              fc_val = old_pin_override.attrib['fc_val']
  
              fc_tag.remove(old_pin_override)
  
              new_attrib = OrderedDict()
              new_attrib["port_name"] = port
              new_attrib["fc_type"] = fc_type
              new_attrib["fc_val"] = fc_val
  
              new_pin_override = ET.SubElement(fc_tag, "fc_override", attrib=new_attrib)
              changed = True
  
          for old_seg_override in old_fc_seg_overrides:
  
              seg_name = old_seg_override.attrib['name']
              in_val = old_seg_override.attrib['in_val']
              out_val = old_seg_override.attrib['out_val']
  
              fc_tag.remove(old_seg_override)
  
              fc_in_type = fc_tag.attrib['default_in_type']
              fc_out_type = fc_tag.attrib['default_out_type']
  
              pb_type = fc_tag.find("..")
              assert pb_type.tag == "pb_type"
  
              inputs = pb_type.findall("./input")
              outputs = pb_type.findall("./output")
              clocks = pb_type.findall("./clock")
  
              for input in inputs + clocks:
  
                  new_attrib = OrderedDict()
                  new_attrib["port_name"] = input.attrib['name']
                  new_attrib["segment_name"] = seg_name
                  new_attrib["fc_type"] = fc_in_type
                  new_attrib["fc_val"] = in_val
  
                  fc_override = ET.SubElement(fc_tag, "fc_override", attrib=new_attrib)
  
              for output in outputs:
  
                  new_attrib = OrderedDict()
                  new_attrib["port_name"] = output.attrib['name']
                  new_attrib["segment_name"] = seg_name
                  new_attrib["fc_type"] = fc_out_type
                  new_attrib["fc_val"] = out_val
  
                  fc_override = ET.SubElement(fc_tag, "fc_override", attrib=new_attrib)
  
              changed = True
      return changed
  
  def upgrade_device_layout(arch):
      """
      Upgrades the legacy <gridlocation> specifications (on each pb_type) to the new format
      placed under the <layout> tag.
      """
      changed = False
  
      #Get the layout tag
      layout = arch.find("./layout")
  
      #Find all the top level pb_types
      top_pb_types = arch.findall("./complexblocklist/pb_type")
  
      type_to_grid_specs = OrderedDict()
      for top_pb_type in top_pb_types:
          type_name = top_pb_type.attrib['name']
  
          assert type_name not in type_to_grid_specs
          type_to_grid_specs[type_name] = []
  
          gridlocations = top_pb_type.find("gridlocations")
          if gridlocations == None:
              continue
  
          for child in gridlocations:
              if child.tag is ET.Comment:
                  continue
              assert child.tag == "loc"
              type_to_grid_specs[type_name].append(child)
  
          #Remove the legacy gridlocations from the <pb_type>
          top_pb_type.remove(gridlocations)
          changed = True
  
      if not changed:
          #No legacy specs to upgrade
          return changed
  
      device_auto = None
      if 'auto' in layout.attrib:
          aspect_ratio = layout.attrib['auto']
  
          del layout.attrib['auto']
          change = True
  
          device_auto = ET.SubElement(layout, 'auto_layout')
          device_auto.attrib['aspect_ratio'] = str(aspect_ratio)
      elif 'width' in layout.attrib and 'height' in layout.attrib:
          width = layout.attrib['width']
          height = layout.attrib['height']
  
          del layout.attrib['width']
          del layout.attrib['height']
          changed = True
  
          device_auto = ET.SubElement(layout, 'fixed_layout')
          device_auto.attrib['name'] = "unnamed_device"
          device_auto.attrib['width'] = width
          device_auto.attrib['height'] = height
      else:
          assert False, "Unrecognized <layout> specification"
  
      if 0:
          for type, locs in type_to_grid_specs.iteritems():
              print "Type:", type
              for loc in locs:
                  print "\t", loc.tag, loc.attrib
  
      have_perimeter = False
  
      for type_name, locs in type_to_grid_specs.iteritems():
          for loc in locs:
              if loc.attrib['type'] == "perimeter":
                  have_perimeter = True
  
  
      if changed:
          layout.text = "\n" + INDENT
          device_auto.text = "\n" + 2*INDENT
          device_auto.tail = "\n"
  
  
      for type_name, locs in type_to_grid_specs.iteritems():
          for loc in locs:
              assert loc.tag == "loc"
  
              loc_type = loc.attrib['type']
  
              #Note that we scale the priority by a factor of 10 to allow us to 'tweak'
              #the priorities without causing conflicts with user defined priorities
              priority = 10*int(loc.attrib['priority'])
  
              if loc_type == "col":
                  start = loc.attrib['start']
  
                  repeat = None
                  if 'repeat' in loc.attrib:
                      repeat = loc.attrib['repeat']
  
                  comment_str = "Column of '{}' with 'EMPTY' blocks wherever a '{}' does not fit.".format(type_name, type_name)
  
                  if have_perimeter:
                      comment_str += " Vertical offset by 1 for perimeter."
  
                  comment = ET.Comment(comment_str)
                  device_auto.append(comment)
                  comment.tail = "\n" + 2*INDENT
  
                  col_spec = ET.SubElement(device_auto, 'col')
  
                  col_spec.attrib['type'] = type_name
                  col_spec.attrib['startx'] = start
                  if have_perimeter:
                      col_spec.attrib['starty'] = "1"
                  if repeat:
                      col_spec.attrib['repeatx'] = repeat
                  col_spec.attrib['priority'] = str(priority)
                  col_spec.tail = "\n" + 2*INDENT
  
                  #Classic VPR fills blank spaces (e.g. where a height > 1 block won't fit) with "EMPTY"
                  #instead of with the underlying type. To replicate that we create a col spec with the same
                  #location information, but of type 'EMPTY' and with slightly lower priority than the real type.
  
                  col_empty_spec = ET.SubElement(device_auto, 'col')
                  col_empty_spec.attrib['type'] = "EMPTY"
                  col_empty_spec.attrib['startx'] = start
                  if repeat:
                      col_empty_spec.attrib['repeatx'] = repeat
                  if have_perimeter:
                      col_empty_spec.attrib['starty'] = "1"
                  col_empty_spec.attrib['priority'] = str(priority - 1) #-1 so it won't override the 'real' col
                  col_empty_spec.tail = "\n" + 2*INDENT
  
              elif loc_type == "rel":
                  pos = loc.attrib['pos']
  
                  div_factor = 1. / float(pos)
  
                  int_div_factor = int(div_factor)
  
                  startx = "(W - 1) / {}".format(div_factor)
  
                  if float(int_div_factor) != div_factor:
                      print "Warning: Relative position factor conversion is not exact. Original pos factor: {}. New startx expression: {}".format(pos, startx)
  
                  comment_str = "Column of '{}' with 'EMPTY' blocks wherever a '{}' does not fit.".format(type_name, type_name)
  
                  if have_perimeter:
                      comment_str += " Vertical offset by 1 for perimeter."
  
                  comment = ET.Comment(comment_str)
                  device_auto.append(comment)
                  comment.tail = "\n" + 2*INDENT
  
                  col_spec = ET.SubElement(device_auto, 'col')
                  col_spec.attrib['type'] = type_name
                  col_spec.attrib['startx'] = startx
                  if have_perimeter:
                      col_spec.attrib['starty'] = "1"
                  col_spec.attrib['priority'] = str(priority)
                  col_spec.tail = "\n" + 2*INDENT
  
                  #Classic VPR fills blank spaces (e.g. where a height > 1 block won't fit) with "EMPTY"
                  #instead of with the underlying type. To replicate that we create a col spec with the same
                  #location information, but of type 'EMPTY' and with slightly lower priority than the real type.
                  col_empty_spec = ET.SubElement(device_auto, 'col')
                  col_empty_spec.attrib['type'] = "EMPTY"
                  col_empty_spec.attrib['startx'] = startx
                  if have_perimeter:
                      col_empty_spec.attrib['starty'] = "1"
                  col_empty_spec.attrib['priority'] = str(priority - 1) #-1 so it won't override the 'real' col
                  col_empty_spec.tail = "\n" + 2*INDENT
  
              elif loc_type == "fill":
  
                  comment = ET.Comment("Fill with '{}'".format(type_name))
                  device_auto.append(comment)
                  comment.tail = "\n" + 2*INDENT
  
                  fill_spec = ET.SubElement(device_auto, 'fill')
                  fill_spec.attrib['type'] = type_name
                  fill_spec.attrib['priority'] = str(priority)
                  fill_spec.tail = "\n" + 2*INDENT
  
              elif loc_type == "perimeter":
                  #The classic VPR perimeter specification did not include the corners (while the new version does)
                  # As a result we specify a full perimeter (including corners), and then apply an EMPTY type override
                  # at the corners
  
                  comment = ET.Comment("Perimeter of '{}' blocks with 'EMPTY' blocks at corners".format(type_name))
                  device_auto.append(comment)
                  comment.tail = "\n" + 2*INDENT
  
                  perim_spec = ET.SubElement(device_auto, 'perimeter')
                  perim_spec.attrib['type'] = type_name
                  perim_spec.attrib['priority'] = str(priority)
                  perim_spec.tail = "\n" + 2*INDENT
  
                  corners_spec = ET.SubElement(device_auto, 'corners')
                  corners_spec.attrib['type'] = "EMPTY"
                  corners_spec.attrib['priority'] = str(priority + 1) #+1 to ensure overrides
                  corners_spec.tail = "\n" + 2*INDENT
  
              else:
                  assert False, "Unrecognzied <loc> type tag {}".format(loc_type)
  
      return changed
  
  def remove_io_chan_distr(arch):
      """
      Removes the legacy '<io>' channel width distribution tags
      """
      device = arch.find("./device")
      chan_width_distr = device.find("./chan_width_distr")
  
      io = chan_width_distr.find("./io")
      if io != None:
          width = float(io.attrib['width'])
  
          if width != 1.:
              print "Found non-unity io width {}. This is no longer supported by VPR and must be manually correct. Exiting...".format(width)
              sys.exit(1)
          else:
              assert width == 1.
              chan_width_distr.remove(io)
              return True #Modified
  
  def upgrade_pinlocations(arch):
      """
      Upgrades custom pin locations from the 'offset' to 'yoffset' attribute.
      Since previously only width==1 blocks were supported, we only need to consider
      the yoffset case
      """
      modified = False
      pinlocations_list = arch.findall(".//pb_type/pinlocations")
  
      for pinlocations in pinlocations_list:
          pb_type = pinlocations.find("..")
  
          assert pb_type.tag == "pb_type"
  
          width = 1
          height = 1
          if 'width' in pb_type.attrib:
              width = int(pb_type.attrib['width'])
          if 'height' in pb_type.attrib:
              height = int(pb_type.attrib['height'])
  
          if width == 1:
  
              if pinlocations.attrib['pattern'] == "custom":
  
                  for loc in pinlocations:
                      if loc.tag is ET.Comment:
                          continue
  
                      assert loc.tag == "loc"
  
                      if 'offset' in loc.attrib:
                          offset = int(loc.attrib['offset'])
  
                          assert offset < height
  
                          #Remove the old attribute
                          del loc.attrib['offset']
  
                          #Add the new attribute
                          loc.attrib['yoffset'] = str(offset)
  
                          modified = True
  
              else:
                  assert pinlocations.attrib['pattern'] == "spread"
  
      return modified
  
  def uniqify_interconnect_names(arch):
      """
      Ensure all interconnect tags have unique names
      """
      modified = False
  
      for interconnect_tag in arch.findall(".//interconnect"):
          seen_names = set()
          cnt = 0
          for child_tag in interconnect_tag:
              if child_tag.tag is ET.Comment:
                  continue
  
              name = orig_name = child_tag.attrib['name']
  
              if orig_name in seen_names:
                  #Generate a unique name
                  while name in seen_names:
                      name = orig_name + "_{}".format(cnt)
                      cnt += 1
  
                  assert name not in seen_names
                  child_tag.attrib['name'] = name
                  modified = True
  
              seen_names.add(name)
  
      return modified
  
  def upgrade_connection_block_input_switch(arch):
      """
      Convert connection block input switch specification to use a switch named
      in <switchlist>
      """
      modified = False
  
      device_tag = arch.find("./device")
      timing_tag = device_tag.find("./timing")
      sizing_tag = device_tag.find("./sizing")
      switchlist_tag = arch.find("./switchlist")
      connection_block_tag = arch.find("./connection_block")
  
      assert sizing_tag is not None
  
      if timing_tag is not None:
          assert sizing_tag is not None
          assert 'ipin_mux_trans_size' in sizing_tag.attrib
          assert 'C_ipin_cblock' in timing_tag.attrib
          assert 'T_ipin_cblock' in timing_tag.attrib
          assert not device_tag.find("./connection_block") is not None
          assert switchlist_tag is not None
  
          R_minW_nmos = sizing_tag.attrib['R_minW_nmos']
          ipin_switch_mux_trans_size = sizing_tag.attrib['ipin_mux_trans_size']
          ipin_switch_cin = timing_tag.attrib['C_ipin_cblock']
          ipin_switch_tdel = timing_tag.attrib['T_ipin_cblock']
  
          modified = True
  
          #Remove the old attributes
          del sizing_tag.attrib['ipin_mux_trans_size']
          device_tag.remove(timing_tag)
  
          #
          #Create the switch
          #
  
          switch_name = "ipin_cblock"
  
          #Make sure the switch name doesn't already exist
          for switch_tag in switchlist_tag.findall("./switch"):
              assert switch_tag.attrib['name'] != switch_name
  
          #Comment the switch
          comment = ET.Comment("switch {} resistance set to yeild for 4x minimum drive strength buffer".format(switch_name))
          comment.tail = "\n" + 2*INDENT
          switchlist_tag.append(comment)
          #Create the switch
          switch_tag = ET.SubElement(switchlist_tag, "switch")
          switch_tag.attrib['type'] = 'mux'
          switch_tag.attrib['name'] = switch_name
          switch_tag.attrib['R'] = str(float(R_minW_nmos) / 4.)
          switch_tag.attrib['Cout'] = "0."
          switch_tag.attrib['Cin'] = ipin_switch_cin
          switch_tag.attrib['Tdel'] = ipin_switch_tdel
          switch_tag.attrib['mux_trans_size'] = ipin_switch_mux_trans_size
          switch_tag.attrib['buf_size'] = "auto"
          switch_tag.tail = "\n" + 2*INDENT
  
          #Create the connection_block tag
          connection_block_tag = ET.SubElement(device_tag, "connection_block")
          connection_block_tag.attrib['input_switch_name'] = switch_name
          connection_block_tag.tail = "\n" + 2*INDENT
  
      else:
          assert 'ipin_switch_mux_trans_size' not in sizing_tag.attrib
  
      return modified
  
  def upgrade_switch_types(arch):
      """
      Rename 'buffered' and 'pass_trans' <switch type=""/> to 'tristate' and 'pass_gate'
      """
      modified = False
      switchlist_tag = arch.find("./switchlist")
      assert switchlist_tag is not None
  
      for switch_tag in switchlist_tag.findall("./switch"):
  
          switch_type = switch_tag.attrib['type']
  
          if switch_type in ['buffered', 'pass_trans']:
              if switch_type == 'buffered':
                  switch_type = "tristate"
              else:
                  assert switch_type == 'pass_trans'
                  switch_type = "pass_gate"
  
              switch_tag.attrib['type'] = switch_type
              modified = True
  
      return modified
  
  def rename_fc_attributes(arch):
      """
      Converts <fc> attributes of the form default_% to %
      """
      fc_tags = arch.findall(".//fc")
  
      changed = False
      for fc_tag in fc_tags:
          assert fc_tag.tag == "fc"
          attr_to_replace = ["default_in_type", "default_in_val", "default_out_type", "default_out_val"]
          attr_list = list(fc_tag.attrib.keys())
          for attr in attr_list:
              if attr in attr_to_replace:
                  val = fc_tag.attrib[attr]
                  del fc_tag.attrib[attr]
                  fc_tag.attrib[attr.replace("default_", "")] = val
                  changed = True
      return changed
  
  def remove_longline_sb_cb(arch):
      """
      Drops <sb> and <cb> of any <segment> types with length="longline",
      since we now assume longlines have full switch block/connection block
      populations
      """
  
      longline_segment_tags = arch.findall(".//segment[@length='longline']")
  
      changed = False
      for longline_segment_tag in longline_segment_tags:
          assert longline_segment_tag.tag == 'segment'
          assert longline_segment_tag.attrib['length'] == 'longline'
  
          child_sb_tags = longline_segment_tag.findall("./sb")
          child_cb_tags = longline_segment_tag.findall("./cb")
  
          for cb_sb_tag in child_sb_tags + child_cb_tags:
              assert cb_sb_tag.tag == 'cb' or cb_sb_tag.tag == 'sb'
              print >>sys.stderr, "Warning: Removing invalid {} tag for longline segment".format(cb_sb_tag.tag)
              longline_segment_tag.remove(cb_sb_tag)
              changed = True
  
      return changed
  
  def upgrade_port_equivalence(arch):
      """
      Upgrades port equivalence from "true|false" to "none|full|instance"
  
      Note that to be safely conservative we upgrade <output equivalent="true">
      to <output equivalent="instance">. Users should check whether their architecture
      should actually be using <output equivalent="full">.
      """
  
      input_equivalent_tags = arch.findall(".//input[@equivalent]")
      output_equivalent_tags = arch.findall(".//output[@equivalent]")
  
      changed = False
  
      for input_equivalent_tag in input_equivalent_tags:
          assert input_equivalent_tag.tag == "input"
          assert 'equivalent' in input_equivalent_tag.attrib
  
          #For inputs, 'true' is upgraded to full, and 'false' to 'none'
          if input_equivalent_tag.attrib['equivalent'] == "true":
              input_equivalent_tag.attrib['equivalent'] = "full"
              changed = True
          elif input_equivalent_tag.attrib['equivalent'] == "false":
              input_equivalent_tag.attrib['equivalent'] = "none"
              changed = True
          else:
              assert input_equivalent_tag.attrib['equivalent'] in ['none', 'full', 'instance'], "equivalence already upgraded"
  
      for output_equivalent_tag in output_equivalent_tags:
          assert output_equivalent_tag.tag == "output"
          assert 'equivalent' in output_equivalent_tag.attrib
  
          #For outputs, 'true' is upgraded to full, and 'false' to 'none'
          if output_equivalent_tag.attrib['equivalent'] == "true":
              parent_tag = output_equivalent_tag.find("..[@name]")
              print >>sys.stderr, "Warning: Conservatively upgrading output port equivalence from 'true' to 'instance' on {}. The user should manually check whether this can be upgraded to 'full'".format(parent_tag.attrib['name'])
              output_equivalent_tag.attrib['equivalent'] = "instance" #Conservative
              changed = True
          elif output_equivalent_tag.attrib['equivalent'] == "false":
              output_equivalent_tag.attrib['equivalent'] = "none"
              changed = True
          else:
              assert output_equivalent_tag.attrib['equivalent'] in ['none', 'full', 'instance'], "equivalence already upgraded"
  
      return changed
  
  def get_port_names(string):
      ports = []
  
      for elem in string.split():
          #Split off the prefix
          port = elem.split('.')[-1]
          if '[' in port:
              port = port[:port.find('[')]
  
          ports.append(port)
  
      return ports
  
  def upgrade_complex_sb_num_conns(arch):
      """
      Upgrades <wireconn> 'num_conns_type' attribute to 'num_conns"
      """
  
      wireconn_tags = arch.findall(".//wireconn[@num_conns_type]")
  
      changed = False
  
      for wireconn_tag in wireconn_tags:
          assert wireconn_tag.tag == "wireconn"
          assert 'num_conns_type' in wireconn_tag.attrib
  
          #For inputs, 'true' is upgraded to full, and 'false' to 'none'
          num_conns_value = wireconn_tag.attrib['num_conns_type']
  
          del wireconn_tag.attrib['num_conns_type']
  
          if num_conns_value == 'min':
              num_conns_value = 'min(from,to)'
          elif num_conns_value == 'max':
              num_conns_value = 'max(from,to)'
  
          wireconn_tag.attrib['num_conns'] = num_conns_value
  
          changed = True
  
      return changed
  
  def add_missing_comb_model_internal_timing_edges(arch):
      """
      For the purposes of constant generator detection every model needs to include
      a set of primtiive internal timing edges.
  
      If we find a combinational model definition which has no internal timing edges,
      we add timing edges between all input and output ports. This is a pessimistic,
      but safe assumption.
  
      Note that if we find any internal timing edges specified we assume the user has
      specified them correctly and do not make any changes to that model
      """
      changed = False
  
      model_tags = arch.findall("./models/model")
  
      for model_tag in model_tags:
  
          input_clock_tags = model_tag.findall("./input_ports/port[@is_clock='1']")
          if len(input_clock_tags) > 0:
              continue #Sequential primitive -- no change
  
          input_tags_with_timing_edges = model_tag.findall("./input_ports/port[@combinational_sink_ports]")
          if len(input_tags_with_timing_edges) > 0:
              continue #Already has internal edges specified -- no change
  
          #Collect the model port definitions
          input_port_tags = model_tag.findall("./input_ports/port")
          output_port_tags = model_tag.findall("./output_ports/port")
  
          #Collect output port names
          output_port_names = []
          for output_port_tag in output_port_tags:
              output_port_names.append(output_port_tag.attrib['name'])
  
          for input_port_tag in input_port_tags:
  
              assert 'combinational_sink_ports' not in input_port_tag
  
              input_port_tag.attrib['combinational_sink_ports'] = " ".join(output_port_names)
  
              print >>sys.stderr, "Warning: Conservatively upgrading combinational sink dependencies for input port '{}' of model '{}' to '{}'. The user should manually check whether a reduced set of sink dependencies can be safely specified.".format(input_port_tag.attrib['name'], model_tag.attrib['name'], input_port_tag.attrib['combinational_sink_ports'])
  
              changed = True
  
      return changed
  
  def add_tile_tags(arch):
      """
      This script is intended to modify the architecture description file to be compliant with
      the new format.
  
      It moves the top level pb_types attributes and tags to the tiles high-level tag.
  
      BEFORE:
      <complexblocklist>
          <pb_type name="BRAM" area="2" height="4" width="1" capacity="1">
              <inputs ... />
              <outputs ... />
              <interconnect ... />
              <fc ... />
              <pinlocations ... />
              <switchblock_locations ... />
          </pb_type>
      </complexblocklist>
  
      AFTER:
      <tiles>
          <tile name="BRAM" area="2" height="4" width="1" capacity="1">
              <inputs ... />
              <outputs ... />
              <fc ... />
              <pinlocations ... />
              <switchblock_locations ... />
              <equivalent_sites>
                  <site pb_type="BRAM"/>
              </equivalent_sites>
          </tile>
      </tiles>
      <complexblocklist
          <pb_type name="BRAM">
              <inputs ... />
              <outputs ... />
              <interconnect ... />
          </pb_type>
      </complexblocklist>
  
      """
  
      TAGS_TO_SWAP = ['fc', 'pinlocations', 'switchblock_locations']
      TAGS_TO_COPY = ['input', 'output', 'clock']
      ATTR_TO_SWAP = ['area', 'height', 'width', 'capacity']
  
      def swap_tags(tile, pb_type):
          # Moving tags from top level pb_type to tile
          for child in pb_type:
              if child.tag in TAGS_TO_SWAP:
                  pb_type.remove(child)
                  tile.append(child)
              if child.tag in TAGS_TO_COPY:
                  child_copy = copy.deepcopy(child)
                  tile.append(child_copy)
  
  
      if arch.findall('./tiles'):
          return False
  
      models = arch.find('./models')
  
      tiles = ET.Element('tiles')
      models.addnext(tiles)
  
      top_pb_types = []
      for pb_type in arch.iter('pb_type'):
          if pb_type.getparent().tag == 'complexblocklist':
              top_pb_types.append(pb_type)
  
      for pb_type in top_pb_types:
          tile = ET.SubElement(tiles, 'tile')
          attrs = pb_type.attrib
  
          for attr in attrs:
              tile.set(attr, pb_type.get(attr))
  
          # Remove attributes of top level pb_types only
          for attr in ATTR_TO_SWAP:
              pb_type.attrib.pop(attr, None)
  
          equivalent_sites = ET.Element("equivalent_sites")
          site = ET.Element("site")
          site.set("pb_type", attrs['name'])
  
          equivalent_sites.append(site)
          tile.append(equivalent_sites)
  
          swap_tags(tile, pb_type)
  
      return True
  
  
  if __name__ == "__main__":
      main()
  