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
+57 -67
View File
@@ -7,18 +7,7 @@
# #
# Copyright 2016 Ettus Research # Copyright 2016 Ettus Research
# #
# This program is free software: you can redistribute it and/or modify # SPDX-License-Identifier: GPL-3.0-or-later
# 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/>.
# #
""" """
Run Vivado builds Run Vivado builds
@@ -39,32 +28,6 @@ READ_TIMEOUT = 0.1 # s
############################################################################# #############################################################################
# The following functions were copied with minor modifications from PyBOMBS: # 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): def which(program):
""" """
Equivalent to Unix' `which` command. Equivalent to Unix' `which` command.
@@ -80,7 +43,6 @@ def which(program):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK) return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
if os.path.split(program)[0] and is_exe(program): if os.path.split(program)[0] and is_exe(program):
return program return program
else:
for path in os.environ.get("PATH", "").split(os.pathsep): for path in os.environ.get("PATH", "").split(os.pathsep):
exe_file = os.path.join(path, program) exe_file = os.path.join(path, program)
if is_exe(exe_file): if is_exe(exe_file):
@@ -94,9 +56,7 @@ def print_timer(time_delta):
"""docstring for print_timer""" """docstring for print_timer"""
hours, secs = divmod(time_delta.seconds, 3600) hours, secs = divmod(time_delta.seconds, 3600)
mins, secs = divmod(secs, 60) mins, secs = divmod(secs, 60)
return "[{h:02}:{m:02}:{s:02}]".format( return f"[{hours:02}:{mins:02}:{secs:02}]"
h=hours, m=mins, s=secs,
)
def list_search(patterns, string): def list_search(patterns, string):
" Returns True if string matches any element of pattern " " Returns True if string matches any element of pattern "
@@ -133,7 +93,7 @@ def parse_args():
our_args, viv_args = parser.parse_known_args() our_args, viv_args = parser.parse_known_args()
return our_args, " ".join(viv_args) return our_args, " ".join(viv_args)
class VivadoRunner(object): class VivadoRunner:
" Vivado Runner " " Vivado Runner "
colors = { colors = {
'warning': '\033[0;35m', 'warning': '\033[0;35m',
@@ -174,6 +134,18 @@ class VivadoRunner(object):
self.notif_queue = Queue() self.notif_queue = Queue()
self.msg_counters = {} self.msg_counters = {}
self.fatal_error_found = False 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 = { self.line_types = {
'cmd': { 'cmd': {
'regexes': [ 'regexes': [
@@ -184,30 +156,42 @@ class VivadoRunner(object):
}, },
'task': { 'task': {
'regexes': [ 'regexes': [
'^Starting .* Task', r'^Starting .* Task',
'^.*Translating synthesized netlist.*', r'^.*Translating synthesized netlist.*',
'^\[TEST CASE .*', r'^\[TEST CASE .*',
], ],
'action': self.update_task, 'action': self.update_task,
'id': "Task", 'id': "Task",
}, },
'phase': { 'phase': {
'regexes': [ 'regexes': [
'^Phase (?P<id>[a-zA-Z0-9/. ]*)$', r'^Phase (?P<id>[a-zA-Z0-9/. ]*)$',
'^Start (?P<id>[a-zA-Z0-9/. ]*)$', r'^Start (?P<id>[a-zA-Z0-9/. ]*)$',
'^(?P<id>TESTBENCH STARTED: [\w_]*)$', r'^(?P<id>TESTBENCH STARTED: [\w_]*)$',
], ],
'action': self.update_phase, 'action': self.update_phase,
'id': "Phase", 'id': "Phase",
}, },
'warning': { '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': [ 'regexes': [
'^WARNING' '^WARNING',
], ],
'action': lambda x: self.act_on_build_msg('warning', x), 'action': lambda x: self.act_on_build_msg('warning', x),
'id': "Warning", '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': { 'critical warning': {
'regexes': [ 'regexes': [
@@ -243,20 +227,20 @@ class VivadoRunner(object):
if args.parse_config is not None: if args.parse_config is not None:
try: try:
args.parse_config = os.path.normpath(args.parse_config) 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( 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') color=self.colors.get('normal')
) )
loadables = ('regexes', 'ignore', 'fatal') 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: for loadable in loadables:
self.line_types[line_type][loadable] = \ line_type[loadable] = line_type.get(loadable, []) + \
self.line_types[line_type].get(loadable, []) + \ parse_config.get(line_type_name, {}).get(loadable, [])
parse_config.get(line_type, {}).get(loadable, [])
except (IOError, ValueError): except (IOError, ValueError):
self.add_notification( 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') color=self.colors.get('warning')
) )
self.tty = sys.stdout.isatty() self.tty = sys.stdout.isatty()
@@ -284,7 +268,8 @@ class VivadoRunner(object):
return "" return ""
# Start process # Start process
self.add_notification( 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( proc = subprocess.Popen(
self.command, self.command,
@@ -297,7 +282,9 @@ class VivadoRunner(object):
# End the thread when the program terminates # End the thread when the program terminates
t.daemon = True t.daemon = True
t.start() 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.daemon = True
status_line_t.start() status_line_t.start()
# Run loop # Run loop
@@ -314,6 +301,9 @@ class VivadoRunner(object):
@staticmethod @staticmethod
def run_loop(func, delay, *args, **kwargs): def run_loop(func, delay, *args, **kwargs):
"""
Runs 'func' in a loop until thread terminates.
"""
while True: while True:
func(*args, **kwargs) func(*args, **kwargs)
time.sleep(delay) time.sleep(delay)
@@ -335,7 +325,7 @@ class VivadoRunner(object):
# Check message counts are within limits # Check message counts are within limits
self.update_phase("Finished") self.update_phase("Finished")
self.add_notification( 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, add_time=True,
color=self.colors.get("task" if success else "error") 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. Identify the current line. Return None if the line can't be identified.
""" """
for line_type in self.line_types: for line_type_name, line_type in self.line_types.items():
for regex in self.line_types[line_type]['regexes']: for regex in line_type['regexes']:
re_obj = re.search(regex, line) re_obj = re.search(regex, line)
if re_obj is not None: 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 return None, None
def update_status_line(self): def update_status_line(self):
@@ -415,7 +405,7 @@ class VivadoRunner(object):
#sys.stdout.write("\n") #sys.stdout.write("\n")
self.add_notification("Executing Tcl: " + tcl_cmd, self.add_notification("Executing Tcl: " + tcl_cmd,
add_time=True, color=self.colors.get("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: if cmd in self.viv_tcl_cmds:
cmd = self.viv_tcl_cmds[cmd] cmd = self.viv_tcl_cmds[cmd]
self.update_task("Starting " + cmd + " Command", is_new=False) self.update_task("Starting " + cmd + " Command", is_new=False)
@@ -467,4 +457,4 @@ def main():
return False return False
if __name__ == "__main__": if __name__ == "__main__":
exit(not main()) sys.exit(not main())