text: #!/usr/bin/env python3
  """
  Creates a device library file
  """
  import argparse
  import sys
  import csv
  import os
  import re
  from collections import namedtuple
  from collections import defaultdict
  from datetime import date
  import simplejson as json
  import lxml.etree as ET
  
  # =============================================================================
  
  # =============================================================================
  """
  Pin properties
  name - pin_name
  dir - direction (input/output)
  used - specify 'yes' if user is using the pin else specify 'no'
  clk - specify associate clock
  """
  PinData = namedtuple("PinData", "name dir used clk")
  
  # =============================================================================
  
  
  def main():
      """
      Creates a device library file by getting data from given csv and xml file
      """
      parser = argparse.ArgumentParser(
          description='Creates a device library file.'
      )
      parser.add_argument(
          "--lib",
          "-l",
          "-L",
          type=str,
          default="qlf_k4n8.lib",
          help='The output device lib file'
      )
      parser.add_argument(
          "--lib_name",
          "-n",
          "-N",
          type=str,
          required=True,
          help='Specify library name'
      )
      parser.add_argument(
          "--device_name",
          "-d",
          "-D",
          type=str,
          required=True,
          help='Specify device name'
      )
      parser.add_argument(
          "--template_data_path",
          "-t",
          "-T",
          type=str,
          required=True,
          help='Specify path from where to pick template data for library creation'
      )
      parser.add_argument(
          "--cell_name",
          "-m",
          "-M",
          type=str,
          required=True,
          help='Specify cell name'
      )
      parser.add_argument(
          "--csv",
          "-c",
          "-C",
          type=str,
          required=True,
          help='Input pin-map csv file'
      )
      parser.add_argument(
          "--xml",
          "-x",
          "-X",
          type=str,
          required=True,
          help='Input interface-mapping xml file'
      )
  
      args = parser.parse_args()
  
      if not os.path.exists(args.template_data_path):
          print(
              'Invalid template data path "{}" specified'.format(
                  args.template_data_path
              ),
              file=sys.stderr
          )
          sys.exit(1)
  
      csv_pin_data = defaultdict(set)
      assoc_clk = dict()
      with open(args.csv, newline='') as csvfile:
          reader = csv.DictReader(csvfile)
          for row in reader:
              # define properties for scalar pins
              scalar_port_names = vec_to_scalar(row['port_name'])
              for port in scalar_port_names:
                  if port.find('F2A') != -1:
                      csv_pin_data['F2A'].add(port)
                  elif port.find('A2F') != -1:
                      csv_pin_data['A2F'].add(port)
                  if row['Associated Clock'] is not None:
                      assoc_clk[port] = row['Associated Clock'].strip()
  
      port_names = parse_xml(args.xml)
      create_lib(
          port_names, args.device_name, args.template_data_path, csv_pin_data,
          args.lib_name, args.lib, args.cell_name, assoc_clk
      )
  
  
  # =============================================================================
  def create_lib(
          port_names, device_name, template_data_path, csv_pin_data, lib_name,
          lib_file_name, cell_name, assoc_clk
  ):
      """
      Create lib file
      """
      # Read header template file and populate lib file with this data
      curr_dir = os.path.dirname(os.path.abspath(__file__))
  
      common_lib_data = dict()
      common_lib_data_file = template_data_path + "/" + device_name + "_common_lib_data.json"
      with open(common_lib_data_file) as fp:
          common_lib_data = json.load(fp)
  
      today = date.today()
      curr_date = today.strftime("%B %d, %Y")
  
      lib_header_tmpl = template_data_path + "/" + device_name + "_lib_header_template.txt"
      header_templ_file = os.path.join(curr_dir, lib_header_tmpl)
      in_str = open(header_templ_file, 'r').read()
  
      curr_str = in_str.replace("{lib_name}", lib_name)
      str_rep_date = curr_str.replace("{curr_date}", curr_date)
      str1 = str_rep_date.replace("{cell_name}", cell_name)
  
      a2f_pin_list = [None] * len(port_names['A2F'])
      f2a_pin_list = [None] * len(port_names['F2A'])
      for port in port_names['A2F']:
          pin_dir = "input"
          pin_used = False
          if port in csv_pin_data['A2F']:
              pin_used = True
  
          clk = ""
          if port in assoc_clk:
              if assoc_clk[port].strip() != '':
                  clk = assoc_clk[port].strip()
  
          index = port_index(port)
          if index != -1:
              pin_data = PinData(name=port, dir=pin_dir, used=pin_used, clk=clk)
              a2f_pin_list[index] = pin_data
          else:
              print("ERROR: No index present in A2F port: '{}'".format(port))
  
      for port in port_names['F2A']:
          pin_dir = "output"
          pin_used = False
          if port in csv_pin_data['F2A']:
              pin_used = True
  
          clk = ""
          if port in assoc_clk:
              if assoc_clk[port].strip() != '':
                  clk = assoc_clk[port].strip()
  
          index = port_index(port)
          if index != -1:
              pin_data = PinData(name=port, dir=pin_dir, used=pin_used, clk=clk)
              f2a_pin_list[index] = pin_data
          else:
              print("ERROR: No index present in F2A port: '{}'\n".format(port))
  
      lib_data = ""
      a2f_bus_name = ""
      if len(a2f_pin_list) > 0:
          pos = a2f_pin_list[0].name.find('[')
          if pos != -1:
              a2f_bus_name = a2f_pin_list[0].name[0:pos]
              lib_data += "\n{}bus ( {} ) {{\n".format(add_tab(2), a2f_bus_name)
              lib_data += "\n{}bus_type   : BUS1536_type1 ;".format(add_tab(3))
              lib_data += "\n{}direction  : input ;\n".format(add_tab(3))
  
      for pin in a2f_pin_list:
          if pin.used:
              curr_str = "\n{}pin ({}) {{".format(add_tab(3), pin.name)
              cap = common_lib_data["input_used"]['cap']
              max_tran = common_lib_data["input_used"]['max_tran']
              curr_str += form_pin_header(pin.dir, cap, max_tran)
  
              if pin.clk != '':
                  clks = pin.clk.split(' ')
                  for clk in clks:
                      clk_name = clk
                      timing_type = ['setup_rising', 'hold_rising']
                      for val in timing_type:
                          curr_str += form_in_timing_group(
                              clk_name, val, common_lib_data
                          )
              curr_str += "\n{}}} /* end of pin {} */\n".format(
                  add_tab(3), pin.name
              )
              lib_data += curr_str
          else:
              curr_str = "\n{}pin ({}) {{".format(add_tab(3), pin.name)
              cap = common_lib_data["input_unused"]['cap']
              max_tran = common_lib_data["input_unused"]['max_tran']
              curr_str += form_pin_header(pin.dir, cap, max_tran)
              curr_str += "\n{}}} /* end of pin {} */\n".format(
                  add_tab(3), pin.name
              )
              lib_data += curr_str
  
      if len(a2f_pin_list) > 0:
          lib_data += "\n{}}} /* end of bus {} */\n".format(
              add_tab(2), a2f_bus_name
          )
  
      f2a_bus_name = ""
      if len(f2a_pin_list) > 0:
          pos = f2a_pin_list[0].name.find('[')
          if pos != -1:
              f2a_bus_name = f2a_pin_list[0].name[0:pos]
              lib_data += "\n{}bus ( {} ) {{\n".format(add_tab(2), f2a_bus_name)
              lib_data += "\n{}bus_type   : BUS1536_type1 ;".format(add_tab(3))
              lib_data += "\n{}direction  : output ;\n".format(add_tab(3))
  
      for pin in f2a_pin_list:
          if pin.used:
              curr_str = "\n{}pin ({}) {{".format(add_tab(3), pin.name)
              cap = common_lib_data["output_used"]['cap']
              max_tran = common_lib_data["output_used"]['max_tran']
              curr_str += form_pin_header(pin.dir, cap, max_tran)
  
              if pin.clk != '':
                  clks = pin.clk.split(' ')
                  for clk in clks:
                      clk_name = clk
                      timing_type = "rising_edge"
                      curr_str += form_out_timing_group(
                          clk_name, timing_type, common_lib_data
                      )
                  curr_str += form_out_reset_timing_group(
                      "RESET_N", "positive_unate", "clear", common_lib_data
                  )
              curr_str += "\n{}}} /* end of pin {} */\n".format(
                  add_tab(3), pin.name
              )
              lib_data += curr_str
          else:
              curr_str = "\n{}pin ({}) {{".format(add_tab(3), pin.name)
              cap = common_lib_data["output_unused"]['cap']
              max_tran = common_lib_data["output_unused"]['max_tran']
              curr_str += form_pin_header(pin.dir, cap, max_tran)
              curr_str += "\n{}}} /* end of pin {} */\n".format(
                  add_tab(3), pin.name
              )
              lib_data += curr_str
      if len(f2a_pin_list) > 0:
          lib_data += "\n{}}} /* end of bus {} */\n".format(
              add_tab(2), f2a_bus_name
          )
  
      dedicated_pin_tmpl = template_data_path + "/" + device_name + '_dedicated_pin_lib_data.txt'
      dedicated_pin_lib_file = os.path.join(curr_dir, dedicated_pin_tmpl)
      dedicated_pin_data = open(dedicated_pin_lib_file, 'r').read()
  
      inter_str = str1.replace("@dedicated_pin_data@", dedicated_pin_data)
  
      final_str = inter_str.replace("@user_pin_data@", lib_data)
      with open(lib_file_name, 'w') as out_fp:
          out_fp.write(final_str)
  
  
  # =============================================================================
  
  
  def port_index(port):
      """
      Returns index in the port name like gfpga_pad_IO_A2F[1248]
      """
      indx_parser = re.compile(r"[a-zA-Z0-9_]*\[(?P<index>[0-9]+)\]$")
      match = indx_parser.fullmatch(port)
      index = -1
      if match is not None:
          index = int(match.group("index"))
  
      return index
  
  
  def form_pin_header(direction, cap, max_tran):
      '''
      Form pin header section
      '''
      curr_str = "\n{}direction : {};\n{}capacitance : {};".format(
          add_tab(4), direction, add_tab(4), cap
      )
      curr_str += "\n{}max_transition : {};".format(add_tab(4), max_tran)
      return curr_str
  
  
  # =============================================================================
  
  
  def form_out_reset_timing_group(
          reset_name, timing_sense, timing_type, common_lib_data
  ):
      '''
      Form timing group for output pin when related pin is reset
      '''
      cell_fall_val = common_lib_data["reset_timing"]['cell_fall_val']
      fall_tran_val = common_lib_data["reset_timing"]['fall_tran_val']
      curr_str = "\n{}timing () {{\n{}related_pin : \"{}\";".format(
          add_tab(4), add_tab(5), reset_name
      )
      curr_str += "\n{}timing_sense : {};".format(add_tab(5), timing_sense)
      curr_str += "\n{}timing_type : {};".format(add_tab(5), timing_type)
      curr_str += "\n{}cell_fall (scalar) {{\n{}values({});\n{}}}".format(
          add_tab(5), add_tab(6), cell_fall_val, add_tab(5)
      )
      curr_str += "\n{}fall_transition (scalar) {{\n{}values({});\n{}}}".format(
          add_tab(5), add_tab(6), fall_tran_val, add_tab(5)
      )
      curr_str += "\n{}}}".format(add_tab(4))
      return curr_str
  
  
  # =============================================================================
  
  
  def form_out_timing_group(clk_name, timing_type, common_lib_data):
      '''
      Form timing group for output pin in a Pin group in a library file
      '''
      cell_rise_val = common_lib_data["output_timing"
                                      ]['rising_edge_cell_rise_val']
      cell_fall_val = common_lib_data["output_timing"
                                      ]['rising_edge_cell_fall_val']
      rise_tran_val = common_lib_data["output_timing"
                                      ]['rising_edge_rise_tran_val']
      fall_tran_val = common_lib_data["output_timing"
                                      ]['rising_edge_fall_tran_val']
      curr_str = "\n{}timing () {{\n{}related_pin : \"{}\";".format(
          add_tab(4), add_tab(5), clk_name
      )
      curr_str += "\n{}timing_type : {};".format(add_tab(5), timing_type)
      curr_str += "\n{}cell_rise (scalar) {{\n{}values({});\n{}}}".format(
          add_tab(5), add_tab(6), cell_rise_val, add_tab(5)
      )
      curr_str += "\n{}rise_transition (scalar) {{\n{}values({});\n{}}}".format(
          add_tab(5), add_tab(6), rise_tran_val, add_tab(5)
      )
      curr_str += "\n{}cell_fall (scalar) {{\n{}values({});\n{}}}".format(
          add_tab(5), add_tab(6), cell_fall_val, add_tab(5)
      )
      curr_str += "\n{}fall_transition (scalar) {{\n{}values({});\n{}}}".format(
          add_tab(5), add_tab(6), fall_tran_val, add_tab(5)
      )
      curr_str += "\n{}}}".format(add_tab(4))
      return curr_str
  
  
  # =============================================================================
  
  
  def add_tab(num_tabs):
      """
      Add given number of tabs and return the string
      """
      curr_str = "\t" * num_tabs
      return curr_str
  
  
  # =============================================================================
  
  
  def form_in_timing_group(clk_name, timing_type, common_lib_data):
      '''
      Form timing group for input pin in a Pin group in a library file
      '''
      rise_constraint_val = "0.0"
      fall_constraint_val = "0.0"
      if timing_type == "setup_rising":
          rise_constraint_val = common_lib_data["input_timing"][
              'setup_rising_rise_constraint_val']
          fall_constraint_val = common_lib_data["input_timing"][
              'setup_rising_fall_constraint_val']
      else:
          rise_constraint_val = common_lib_data["input_timing"][
              'hold_rising_rise_constraint_val']
          fall_constraint_val = common_lib_data["input_timing"][
              'hold_rising_fall_constraint_val']
  
      curr_str = "\n{}timing () {{\n{}related_pin : \"{}\";".format(
          add_tab(4), add_tab(5), clk_name
      )
      curr_str += "\n{}timing_type : {};".format(add_tab(5), timing_type)
      curr_str += "\n{}rise_constraint (scalar) {{\n{}values({});\n{}}}".format(
          add_tab(5), add_tab(6), rise_constraint_val, add_tab(5)
      )
      curr_str += "\n{}fall_constraint (scalar) {{\n{}values({});\n{}}}".format(
          add_tab(5), add_tab(6), fall_constraint_val, add_tab(5)
      )
      curr_str += "\n{}}}".format(add_tab(4))
      return curr_str
  
  
  # =============================================================================
  
  
  def parse_xml(xml_file):
      """
      Parses given xml file and collects the desired data
      """
      parser = ET.XMLParser(resolve_entities=False, strip_cdata=False)
      xml_tree = ET.parse(xml_file, parser)
      xml_root = xml_tree.getroot()
  
      port_names = defaultdict(set)
      # Get the "IO" section
      xml_io = xml_root.find("IO")
      if xml_io is None:
          print("ERROR: No mandatory 'IO' section defined in 'DEVICE' section")
          sys.exit(1)
  
      xml_top_io = xml_io.find("TOP_IO")
      if xml_top_io is not None:
          port_names = parse_xml_io(xml_top_io, port_names)
  
      xml_bottom_io = xml_io.find("BOTTOM_IO")
      if xml_bottom_io is not None:
          port_names = parse_xml_io(xml_bottom_io, port_names)
  
      xml_left_io = xml_io.find("LEFT_IO")
      if xml_left_io is not None:
          port_names = parse_xml_io(xml_left_io, port_names)
  
      xml_right_io = xml_io.find("RIGHT_IO")
      if xml_right_io is not None:
          port_names = parse_xml_io(xml_right_io, port_names)
  
      return port_names
  
  
  # =============================================================================
  
  
  def parse_xml_io(xml_io, port_names):
      """
      Parses xml and get data for mapped_name key
      """
      assert xml_io is not None
      for xml_cell in xml_io.findall("CELL"):
          mapped_name = xml_cell.get("mapped_name")
          # define properties for scalar pins
          scalar_mapped_pins = vec_to_scalar(mapped_name)
          if mapped_name.find('F2A') != -1:
              port_names['F2A'].update(scalar_mapped_pins)
          elif mapped_name.find('A2F') != -1:
              port_names['A2F'].update(scalar_mapped_pins)
      return port_names
  
  
  # =============================================================================
  
  
  def vec_to_scalar(port_name):
      """
      Converts given bus port into a list of its scalar port equivalents
      """
      scalar_ports = []
      if port_name is not None and ':' in port_name:
          open_brace = port_name.find('[')
          close_brace = port_name.find(']')
          if open_brace == -1 or close_brace == -1:
              print(
                  'Invalid portname "{}" specified. Bus ports should contain [ ] to specify range'
                  .format(port_name),
                  file=sys.stderr
              )
              sys.exit(1)
          bus = port_name[open_brace + 1:close_brace]
          lsb = int(bus[:bus.find(':')])
          msb = int(bus[bus.find(':') + 1:])
          if lsb > msb:
              for i in range(lsb, msb - 1, -1):
                  curr_port_name = port_name[:open_brace] + '[' + str(i) + ']'
                  scalar_ports.append(curr_port_name)
          else:
              for i in range(lsb, msb + 1):
                  curr_port_name = port_name[:open_brace] + '[' + str(i) + ']'
                  scalar_ports.append(curr_port_name)
      else:
          scalar_ports.append(port_name)
  
      return scalar_ports
  
  
  # =============================================================================
  
  if __name__ == '__main__':
      main()
  