fpga: tools: Spruce up launch_vivado.py

- We add documentation for the dev_config.json file
- Fix Pylint issues


Original-commit: 7789c136fbf996451b8621da4b277a59193913fe
This commit is contained in:
Martin Braun
2023-12-13 17:28:09 -06:00
committed by Wade Fife
parent 7a00849c02
commit 27b06f86fb
+61 -71
View File
@@ -7,18 +7,7 @@
#
# Copyright 2016 Ettus Research
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Run Vivado builds
@@ -39,32 +28,6 @@ READ_TIMEOUT = 0.1 # s
#############################################################################
# The following functions were copied with minor modifications from PyBOMBS:
def get_console_width():
'''
Returns width of console.
http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python
'''
env = os.environ
def ioctl_GWINSZ(fd):
try:
import fcntl, termios, struct
cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
except:
return
return cr
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
if not cr:
try:
fd = os.open(os.ctermid(), os.O_RDONLY)
cr = ioctl_GWINSZ(fd)
os.close(fd)
except:
pass
if not cr:
cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
return cr[1]
def which(program):
"""
Equivalent to Unix' `which` command.
@@ -80,11 +43,10 @@ def which(program):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
if os.path.split(program)[0] and is_exe(program):
return program
else:
for path in os.environ.get("PATH", "").split(os.pathsep):
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
for path in os.environ.get("PATH", "").split(os.pathsep):
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
#
# End of functions copied from PyBOMBS.
@@ -94,9 +56,7 @@ def print_timer(time_delta):
"""docstring for print_timer"""
hours, secs = divmod(time_delta.seconds, 3600)
mins, secs = divmod(secs, 60)
return "[{h:02}:{m:02}:{s:02}]".format(
h=hours, m=mins, s=secs,
)
return f"[{hours:02}:{mins:02}:{secs:02}]"
def list_search(patterns, string):
" Returns True if string matches any element of pattern "
@@ -133,7 +93,7 @@ def parse_args():
our_args, viv_args = parser.parse_known_args()
return our_args, " ".join(viv_args)
class VivadoRunner(object):
class VivadoRunner:
" Vivado Runner "
colors = {
'warning': '\033[0;35m',
@@ -174,6 +134,18 @@ class VivadoRunner(object):
self.notif_queue = Queue()
self.msg_counters = {}
self.fatal_error_found = False
# This is a list of types of lines that the Vivado build can output.
# All of these can be amended (within limits) by the dev_config.json
# file that is part of a devices top-level directory (e.g., usrp3/top/x400).
#
# The JSON file may contain 'regexes', 'ignore', or 'fatal' entries for
# any of these line types. The entries hard-coded in this file are
# retained (they are not overwritten by the JSON file).
#
# See the entry for 'warning' for additional documentation.
#
# Every line that Vivado outputs can only be of one line type. Regular
# expressions are tested in the order they are given here.
self.line_types = {
'cmd': {
'regexes': [
@@ -184,30 +156,42 @@ class VivadoRunner(object):
},
'task': {
'regexes': [
'^Starting .* Task',
'^.*Translating synthesized netlist.*',
'^\[TEST CASE .*',
r'^Starting .* Task',
r'^.*Translating synthesized netlist.*',
r'^\[TEST CASE .*',
],
'action': self.update_task,
'id': "Task",
},
'phase': {
'regexes': [
'^Phase (?P<id>[a-zA-Z0-9/. ]*)$',
'^Start (?P<id>[a-zA-Z0-9/. ]*)$',
'^(?P<id>TESTBENCH STARTED: [\w_]*)$',
r'^Phase (?P<id>[a-zA-Z0-9/. ]*)$',
r'^Start (?P<id>[a-zA-Z0-9/. ]*)$',
r'^(?P<id>TESTBENCH STARTED: [\w_]*)$',
],
'action': self.update_phase,
'id': "Phase",
},
'warning': {
# This is a list of regular expressions that identify a Vivado
# output line as a warning. Careful: if we choose a regex that
# identifies a critical warning here, then such lines will never
# be classified as 'critical warning' below.
'regexes': [
'^WARNING'
'^WARNING',
],
'action': lambda x: self.act_on_build_msg('warning', x),
'id': "Warning",
'fatal': [
]
# The 'fatal' list is a list of regular expressions that identify
# a warning as fatal, even if Vivado is OK with the warning.
# This list can be amended by dev_config.json.
'fatal': [],
# The 'ignore' list is a list of regular expressions that identify
# a warning as unimportant. These will not be printed. Use this
# for warnings that are unavoidable, e.g., because they come
# from Vivado IP and we have no way to fix them.
# This list can be amended by dev_config.json.
'ignore': [],
},
'critical warning': {
'regexes': [
@@ -243,20 +227,20 @@ class VivadoRunner(object):
if args.parse_config is not None:
try:
args.parse_config = os.path.normpath(args.parse_config)
parse_config = json.load(open(args.parse_config))
with open(args.parse_config, encoding="utf-8") as conf_file:
parse_config = json.load(conf_file)
self.add_notification(
"Using parser configuration from: {pc}".format(pc=args.parse_config),
f"Using parser configuration from: {args.parse_config}",
color=self.colors.get('normal')
)
loadables = ('regexes', 'ignore', 'fatal')
for line_type in self.line_types:
for line_type_name, line_type in self.line_types.items():
for loadable in loadables:
self.line_types[line_type][loadable] = \
self.line_types[line_type].get(loadable, []) + \
parse_config.get(line_type, {}).get(loadable, [])
line_type[loadable] = line_type.get(loadable, []) + \
parse_config.get(line_type_name, {}).get(loadable, [])
except (IOError, ValueError):
self.add_notification(
"Could not read parser configuration from: {pc}".format(pc=args.parse_config),
f"Could not read parser configuration from: {args.parse_config}",
color=self.colors.get('warning')
)
self.tty = sys.stdout.isatty()
@@ -284,7 +268,8 @@ class VivadoRunner(object):
return ""
# Start process
self.add_notification(
"Executing command: {cmd}".format(cmd=self.command), add_time=True, color=self.colors.get('cmd')
f"Executing command: {self.command}",
add_time=True, color=self.colors.get('cmd')
)
proc = subprocess.Popen(
self.command,
@@ -297,7 +282,9 @@ class VivadoRunner(object):
# End the thread when the program terminates
t.daemon = True
t.start()
status_line_t = threading.Thread(target=VivadoRunner.run_loop, args=(self.print_status_line, 0.5 if self.tty else 60*10))
status_line_t = threading.Thread(
target=VivadoRunner.run_loop,
args=(self.print_status_line, 0.5 if self.tty else 60*10))
status_line_t.daemon = True
status_line_t.start()
# Run loop
@@ -314,6 +301,9 @@ class VivadoRunner(object):
@staticmethod
def run_loop(func, delay, *args, **kwargs):
"""
Runs 'func' in a loop until thread terminates.
"""
while True:
func(*args, **kwargs)
time.sleep(delay)
@@ -335,7 +325,7 @@ class VivadoRunner(object):
# Check message counts are within limits
self.update_phase("Finished")
self.add_notification(
"Process terminated. Status: {status}".format(status='Success' if success else 'Failure'),
"Process terminated. Status: " + ('Success' if success else 'Failure'),
add_time=True,
color=self.colors.get("task" if success else "error")
)
@@ -362,11 +352,11 @@ class VivadoRunner(object):
"""
Identify the current line. Return None if the line can't be identified.
"""
for line_type in self.line_types:
for regex in self.line_types[line_type]['regexes']:
for line_type_name, line_type in self.line_types.items():
for regex in line_type['regexes']:
re_obj = re.search(regex, line)
if re_obj is not None:
return line_type, re_obj.groupdict().get('id', line)
return line_type_name, re_obj.groupdict().get('id', line)
return None, None
def update_status_line(self):
@@ -415,7 +405,7 @@ class VivadoRunner(object):
#sys.stdout.write("\n")
self.add_notification("Executing Tcl: " + tcl_cmd,
add_time=True, color=self.colors.get("cmd"))
cmd = tcl_cmd.strip().split()[0];
cmd = tcl_cmd.strip().split()[0]
if cmd in self.viv_tcl_cmds:
cmd = self.viv_tcl_cmds[cmd]
self.update_task("Starting " + cmd + " Command", is_new=False)
@@ -467,4 +457,4 @@ def main():
return False
if __name__ == "__main__":
exit(not main())
sys.exit(not main())