blob: e4c452b4dff9386aeee0ff62a60b8f47084eac8f [file] [log] [blame]
#!/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()