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:
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user