| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| # | |
| # Copyright (C) 2022 F4PGA Authors | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| # SPDX-License-Identifier: Apache-2.0 | |
| """ | |
| 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() |