| #!/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( |
| "--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.template_data_path, csv_pin_data, args.lib_name, |
| args.lib, args.cell_name, assoc_clk |
| ) |
| |
| |
| # ============================================================================= |
| def create_lib( |
| port_names, 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 = os.path.join( |
| template_data_path, "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 = os.path.join( |
| template_data_path, "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 = os.path.join( |
| template_data_path, '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() |