fpga: tools: Refactor viv_ip_xci_editor.py
This Python utility was getting a bit rotten. The following updates where made: - Reformatted using ni-python-styleguide - Fixed formatting of regex strings by declaring them 'raw' - Updated docstrings and any linter issues On Pyton 3.12 and beyond, this will no longer throw SyntaxError warnings. Original-commit: 0dede88c6535ae6ffb30b162c83dc01d59d3bfa0
This commit is contained in:
committed by
Jörg Hofrichter
parent
8e0831b348
commit
4d5cad3310
@@ -1,132 +1,242 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
"""Tooling to handle .xci files for Vivado IP cores."""
|
||||||
|
#
|
||||||
|
# Copyright 2020 Ettus Research, a National Instruments Company
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
# Parse command line options
|
# Parse command line options
|
||||||
def get_options():
|
def parse_args():
|
||||||
parser = argparse.ArgumentParser(description='Utility script to query and modify a Xilinx IP XCI file')
|
"""Parse command line arguments."""
|
||||||
parser.add_argument('action', type=str, default=None, help='Action to perform')
|
parser = argparse.ArgumentParser(
|
||||||
parser.add_argument('xci_filepath', type=str, default=None, help='Name for the IP core')
|
description="Utility script to query and modify a Xilinx IP XCI file"
|
||||||
parser.add_argument('--target', type=str, default=None, help='Input value for target. Must be of the form <arch>/<device>/<package>/<speedgrade>/<silicon revision>')
|
)
|
||||||
parser.add_argument('--name', type=str, default=None, help='Input value for new IP name')
|
parser.add_argument(
|
||||||
parser.add_argument("--output_dir", type=str, default='.', help="Build directory for IP")
|
"action",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Action to perform",
|
||||||
|
choices=["read_target", "read_arch", "read_partid", "read_part", "retarget", "rename"],
|
||||||
|
)
|
||||||
|
parser.add_argument("xci_filepath", type=str, default=None, help="Name for the IP core")
|
||||||
|
parser.add_argument(
|
||||||
|
"--target",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Input value for target. Must be of the form <arch>/<device>/<package>/<speedgrade>/<silicon revision>",
|
||||||
|
)
|
||||||
|
parser.add_argument("--name", type=str, default=None, help="Input value for new IP name")
|
||||||
|
parser.add_argument("--output_dir", type=str, default=".", help="Build directory for IP")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if not args.action:
|
if not args.action:
|
||||||
print('ERROR: Please specify an action to perform\n')
|
print("ERROR: Please specify an action to perform\n")
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if not args.xci_filepath:
|
if not args.xci_filepath:
|
||||||
print('ERROR: Please specify the location for the XCI file to operate on\n')
|
print("ERROR: Please specify the location for the XCI file to operate on\n")
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if not os.path.isfile(args.xci_filepath):
|
if not os.path.isfile(args.xci_filepath):
|
||||||
print('ERROR: XCI File ' + args.xci_filepath + ' could not be accessed or is not a file.\n')
|
print(f"ERROR: XCI File {args.xci_filepath} could not be accessed or is not a file.\n")
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def get_top_level_match_str(tag_text):
|
def get_top_level_match_str(tag_text):
|
||||||
return '(.*\<' + tag_text + '\>)(.+)(\</' + tag_text + '\>)'
|
"""Get the regex string to match a top-level tag."""
|
||||||
|
return r"(.*\<" + tag_text + "\>)(.+)(\</" + tag_text + r"\>)"
|
||||||
|
|
||||||
|
|
||||||
def get_match_str(item):
|
def get_match_str(item):
|
||||||
return '(.*\<spirit:configurableElementValue spirit:referenceId=\".*\.' + item + '\"\>)(.+)(\</spirit:configurableElementValue\>)'
|
"""Get the regex string to match a configurable element value."""
|
||||||
|
return (
|
||||||
|
r'(.*\<spirit:configurableElementValue spirit:referenceId=".*\.'
|
||||||
|
+ item
|
||||||
|
+ r'"\>)(.+)(\</spirit:configurableElementValue\>)'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_empty_match_str(item):
|
def get_empty_match_str(item):
|
||||||
return '(.*\<spirit:configurableElementValue spirit:referenceId=\".*\.' + item + '\")/\>'
|
"""Get the regex string to match an empty configurable element value."""
|
||||||
|
return r'(.*\<spirit:configurableElementValue spirit:referenceId=".*\.' + item + r'")/\>'
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = get_options();
|
"""Main function."""
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
# Read XCI File
|
# Read XCI File
|
||||||
with open(args.xci_filepath) as in_file:
|
with open(args.xci_filepath) as in_file:
|
||||||
xci_lines = in_file.readlines()
|
xci_lines = in_file.readlines()
|
||||||
|
|
||||||
if args.action.startswith('read_'):
|
if args.action.startswith("read_"):
|
||||||
# Extract info from XCI File
|
# Extract info from XCI File
|
||||||
xci_info = dict()
|
xci_info = dict()
|
||||||
for line in xci_lines:
|
for line in xci_lines:
|
||||||
m = re.search(get_match_str('(ARCHITECTURE|DEVICE|PACKAGE|SPEEDGRADE|TEMPERATURE_GRADE|SILICON_REVISION)'), line)
|
m = re.search(
|
||||||
|
get_match_str(
|
||||||
|
"(ARCHITECTURE|DEVICE|PACKAGE|SPEEDGRADE|TEMPERATURE_GRADE|SILICON_REVISION)"
|
||||||
|
),
|
||||||
|
line,
|
||||||
|
)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
xci_info[m.group(2)] = m.group(3)
|
xci_info[m.group(2)] = m.group(3)
|
||||||
else:
|
else:
|
||||||
m = re.search(get_empty_match_str('(ARCHITECTURE|DEVICE|PACKAGE|SPEEDGRADE|TEMPERATURE_GRADE|SILICON_REVISION)'),line)
|
m = re.search(
|
||||||
if m is not None:
|
get_empty_match_str(
|
||||||
xci_info[m.group(2)] = ''
|
"(ARCHITECTURE|DEVICE|PACKAGE|SPEEDGRADE|TEMPERATURE_GRADE|SILICON_REVISION)"
|
||||||
if args.action == 'read_target':
|
),
|
||||||
print(xci_info['ARCHITECTURE'] + '/' + xci_info['DEVICE'] + '/' + xci_info['PACKAGE'] + '/' + xci_info['SPEEDGRADE'])
|
line,
|
||||||
if args.action == 'read_arch':
|
)
|
||||||
print(xci_info['ARCHITECTURE'])
|
if m is not None:
|
||||||
if args.action == 'read_partid':
|
xci_info[m.group(2)] = ""
|
||||||
print(xci_info['DEVICE'] + '/' + xci_info['PACKAGE'] + '/' + xci_info['SPEEDGRADE'] + '/' + xci_info['TEMPERATURE_GRADE'] + '/' + xci_info['SILICON_REVISION'])
|
if args.action == "read_target":
|
||||||
if args.action == 'read_part':
|
print(
|
||||||
# The UltraScale+ RFSoC family ids are expected diferently in Vivado, a '-' must separate each property.
|
xci_info["ARCHITECTURE"]
|
||||||
if xci_info['ARCHITECTURE'] == "zynquplusRFSOC":
|
+ "/"
|
||||||
print(xci_info['DEVICE'] + "-" + xci_info['PACKAGE'] + xci_info['SPEEDGRADE'] + "-" + xci_info['TEMPERATURE_GRADE'])
|
+ xci_info["DEVICE"]
|
||||||
|
+ "/"
|
||||||
|
+ xci_info["PACKAGE"]
|
||||||
|
+ "/"
|
||||||
|
+ xci_info["SPEEDGRADE"]
|
||||||
|
)
|
||||||
|
if args.action == "read_arch":
|
||||||
|
print(xci_info["ARCHITECTURE"])
|
||||||
|
if args.action == "read_partid":
|
||||||
|
print(
|
||||||
|
xci_info["DEVICE"]
|
||||||
|
+ "/"
|
||||||
|
+ xci_info["PACKAGE"]
|
||||||
|
+ "/"
|
||||||
|
+ xci_info["SPEEDGRADE"]
|
||||||
|
+ "/"
|
||||||
|
+ xci_info["TEMPERATURE_GRADE"]
|
||||||
|
+ "/"
|
||||||
|
+ xci_info["SILICON_REVISION"]
|
||||||
|
)
|
||||||
|
if args.action == "read_part":
|
||||||
|
# The UltraScale+ RFSoC family IDs are expected differently in
|
||||||
|
# Vivado, a '-' must separate each property.
|
||||||
|
if xci_info["ARCHITECTURE"] == "zynquplusRFSOC":
|
||||||
|
print(
|
||||||
|
xci_info["DEVICE"]
|
||||||
|
+ "-"
|
||||||
|
+ xci_info["PACKAGE"]
|
||||||
|
+ xci_info["SPEEDGRADE"]
|
||||||
|
+ "-"
|
||||||
|
+ xci_info["TEMPERATURE_GRADE"]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print(xci_info['DEVICE'] + xci_info['PACKAGE'] + xci_info['SPEEDGRADE'])
|
print(xci_info["DEVICE"] + xci_info["PACKAGE"] + xci_info["SPEEDGRADE"])
|
||||||
elif args.action == 'retarget':
|
elif args.action == "retarget":
|
||||||
# Write a new XCI file with modified target info
|
# Write a new XCI file with modified target info
|
||||||
if not os.path.isdir(args.output_dir):
|
if not os.path.isdir(args.output_dir):
|
||||||
print('ERROR: IP Build directory ' + args.output_dir + ' could not be accessed or is not a directory.')
|
print(
|
||||||
|
"ERROR: IP Build directory "
|
||||||
|
+ args.output_dir
|
||||||
|
+ " could not be accessed or is not a directory."
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if not args.target:
|
if not args.target:
|
||||||
print('ERROR: No target specified.')
|
print("ERROR: No target specified.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
target_tok = args.target.split('/')
|
target_tok = args.target.split("/")
|
||||||
if len(target_tok) < 4:
|
if len(target_tok) < 4:
|
||||||
print('ERROR: Invalid target format. Must be <arch>/<device>/<package>/<speedgrade>/<tempgrade>/<silicon revision>')
|
print(
|
||||||
|
"ERROR: Invalid target format. "
|
||||||
|
"Must be <arch>/<device>/<package>/<speedgrade> or "
|
||||||
|
"<arch>/<device>/<package>/<speedgrade>/<tempgrade>/<silicon revision>"
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
replace_dict = {'ARCHITECTURE': target_tok[0], 'DEVICE': target_tok[1], 'PACKAGE': target_tok[2], 'SPEEDGRADE': target_tok[3], \
|
replace_dict = {
|
||||||
'C_XDEVICEFAMILY': target_tok[0], 'C_FAMILY': target_tok[0], 'C_XDEVICE': target_tok[1]}
|
"ARCHITECTURE": target_tok[0],
|
||||||
|
"DEVICE": target_tok[1],
|
||||||
|
"PACKAGE": target_tok[2],
|
||||||
|
"SPEEDGRADE": target_tok[3],
|
||||||
|
"C_XDEVICEFAMILY": target_tok[0],
|
||||||
|
"C_FAMILY": target_tok[0],
|
||||||
|
"C_XDEVICE": target_tok[1],
|
||||||
|
}
|
||||||
if len(target_tok) > 4:
|
if len(target_tok) > 4:
|
||||||
replace_dict['TEMPERATURE_GRADE'] = target_tok[4]
|
replace_dict["TEMPERATURE_GRADE"] = target_tok[4]
|
||||||
if len(target_tok) > 5:
|
if len(target_tok) > 5:
|
||||||
replace_dict['SILICON_REVISION'] = target_tok[5]
|
replace_dict["SILICON_REVISION"] = target_tok[5]
|
||||||
out_xci_filename = os.path.join(os.path.abspath(args.output_dir), os.path.basename(args.xci_filepath))
|
out_xci_filename = os.path.join(
|
||||||
|
os.path.abspath(args.output_dir), os.path.basename(args.xci_filepath)
|
||||||
|
)
|
||||||
|
|
||||||
with open(out_xci_filename, 'w') as out_file:
|
with open(out_xci_filename, "w") as out_file:
|
||||||
for r_line in xci_lines:
|
for r_line in xci_lines:
|
||||||
w_line = r_line
|
w_line = r_line
|
||||||
m = re.search(get_match_str('(' + '|'.join(list(replace_dict.keys())) + ')'), r_line)
|
m = re.search(
|
||||||
|
get_match_str("(" + "|".join(list(replace_dict.keys())) + ")"), r_line
|
||||||
|
)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
w_line = m.group(1) + replace_dict[m.group(2)] + m.group(4) +'\n'
|
w_line = m.group(1) + replace_dict[m.group(2)] + m.group(4) + "\n"
|
||||||
else:
|
else:
|
||||||
m = re.search(get_empty_match_str('(' + '|'.join(list(replace_dict.keys())) + ')'), r_line)
|
m = re.search(
|
||||||
|
get_empty_match_str("(" + "|".join(list(replace_dict.keys())) + ")"), r_line
|
||||||
|
)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
w_line = m.group(1) + '>' + replace_dict[m.group(2)] + '</spirit:configurableElementValue>\n'
|
w_line = (
|
||||||
|
m.group(1)
|
||||||
|
+ ">"
|
||||||
|
+ replace_dict[m.group(2)]
|
||||||
|
+ "</spirit:configurableElementValue>\n"
|
||||||
|
)
|
||||||
out_file.write(w_line)
|
out_file.write(w_line)
|
||||||
elif args.action == 'rename':
|
elif args.action == "rename":
|
||||||
# Write a new XCI file with a new name
|
# Write a new XCI file with a new name
|
||||||
if not os.path.isdir(args.output_dir):
|
if not os.path.isdir(args.output_dir):
|
||||||
print('ERROR: IP Build directory ' + args.output_dir + ' could not be accessed or is not a directory.')
|
print(
|
||||||
|
"ERROR: IP Build directory "
|
||||||
|
+ args.output_dir
|
||||||
|
+ " could not be accessed or is not a directory."
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if not args.name:
|
if not args.name:
|
||||||
print('ERROR: No name specified.')
|
print("ERROR: No name specified.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
new_name = args.name
|
new_name = args.name
|
||||||
|
|
||||||
replace_dict = {'Component_Name': new_name}
|
replace_dict = {"Component_Name": new_name}
|
||||||
replace_top_level = ('spirit:instanceName', new_name)
|
replace_top_level = ("spirit:instanceName", new_name)
|
||||||
out_xci_filename = os.path.join(os.path.abspath(args.output_dir), new_name + '.xci')
|
out_xci_filename = os.path.join(os.path.abspath(args.output_dir), new_name + ".xci")
|
||||||
|
|
||||||
with open(out_xci_filename, 'w') as out_file:
|
with open(out_xci_filename, "w") as out_file:
|
||||||
for r_line in xci_lines:
|
for r_line in xci_lines:
|
||||||
w_line = r_line
|
w_line = r_line
|
||||||
m = re.search(get_match_str('(' + '|'.join(list(replace_dict.keys())) + ')'), r_line)
|
m = re.search(
|
||||||
|
get_match_str("(" + "|".join(list(replace_dict.keys())) + ")"), r_line
|
||||||
|
)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
w_line = m.group(1) + replace_dict[m.group(2)] + m.group(4) +'\n'
|
w_line = m.group(1) + replace_dict[m.group(2)] + m.group(4) + "\n"
|
||||||
else:
|
else:
|
||||||
m = re.search(get_empty_match_str('(' + '|'.join(list(replace_dict.keys())) + ')'), r_line)
|
m = re.search(
|
||||||
|
get_empty_match_str("(" + "|".join(list(replace_dict.keys())) + ")"), r_line
|
||||||
|
)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
w_line = m.group(1) + '>' + replace_dict[m.group(2)] + '</spirit:configurableElementValue>\n'
|
w_line = (
|
||||||
|
m.group(1)
|
||||||
|
+ ">"
|
||||||
|
+ replace_dict[m.group(2)]
|
||||||
|
+ "</spirit:configurableElementValue>\n"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
m = re.search(get_top_level_match_str(replace_top_level[0]), r_line)
|
m = re.search(get_top_level_match_str(replace_top_level[0]), r_line)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
w_line = m.group(1) + replace_top_level[1] + m.group(3) + '\n'
|
w_line = m.group(1) + replace_top_level[1] + m.group(3) + "\n"
|
||||||
out_file.write(w_line)
|
out_file.write(w_line)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user