Move sv2v script into standalone core file

FuseSoC script to run sv2v can be reused with this change.
This commit is contained in:
Tobias Wölfel 2020-09-15 14:34:24 +02:00 committed by Rupert Swarbrick
parent 8edcb088da
commit c8d4f2d950
5 changed files with 50 additions and 32 deletions

190
util/sv2v_in_place.py Executable file
View file

@ -0,0 +1,190 @@
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
import argparse
import logging
import os
import re
import shlex
import shutil
import subprocess
import tempfile
from typing import List, Pattern, Tuple
def read_file_list(path: str) -> List[str]:
'''Read in a list of paths from a file, one per line.'''
ret = []
with open(path) as handle:
for line in handle:
ret.append(line.strip())
return ret
def transform_one(sv2v: str,
defines: List[str],
incdirs: List[str],
pkg_paths: List[str],
sv_path: str,
dst_path: str) -> None:
'''Run sv2v to edit a file in place'''
defines_args = ['--define=' + d for d in defines]
incdirs_args = ['--incdir=' + d for d in incdirs]
paths = pkg_paths + ([] if sv_path in pkg_paths else [sv_path])
cmd = ([sv2v,
# Pass --exclude=assert to tell sv2v not to strip out assertions.
# Since the whole point of this flow is to prove assertions, we
# need to leave them unscathed!
'--exclude=assert'] +
defines_args +
incdirs_args +
paths)
logging.info('Running sv2v on {}'.format(sv_path))
logging.debug('Command: {}'.format(cmd))
with open(dst_path, 'w') as dst_file:
proc = subprocess.run(cmd, stdout=dst_file)
if proc.returncode != 0:
cmd_str = ' '.join([shlex.quote(a) for a in cmd])
raise RuntimeError('Failed to run sv2v on {}. '
'Exit code: {}. Full command: {}'
.format(sv_path, proc.returncode, cmd_str))
def parse_define_if(arg: str) -> Tuple[Pattern[str], str]:
'''Handle a --define-if argument'''
parts = arg.rsplit(':', 1)
if len(parts) != 2:
msg = ('The --define-if argument {!r} contains no colon. The correct '
'syntax is "--define-if regex:define".'
.format(arg))
raise argparse.ArgumentTypeError(msg)
re_str, define = parts
try:
return (re.compile(re_str), define)
except re.error as err:
raise argparse.ArgumentTypeError('The regex for the --define-if '
'argument ({!r}) is malformed: {}.'
.format(re_str, err))
def transform(sv2v: str,
defines: List[str],
defines_if: List[Tuple[Pattern[str], str]],
incdirs: List[str],
pkg_paths: List[str],
sv_paths: List[str]) -> None:
'''Run sv2v to transform a list of files in-place'''
with tempfile.TemporaryDirectory() as tmpdir:
# First write each file to a file in a temporary directory, then copy
# everything back. We have to do it like this because otherwise we
# might trash a file that needs to be included by a later one.
dst_paths = []
for idx, src_path in enumerate(sv_paths):
dst_path = os.path.join(tmpdir, str(idx))
extra_file_defines = []
for regex, define in defines_if:
if regex.search(src_path):
extra_file_defines.append(define)
transform_one(sv2v, defines + extra_file_defines,
incdirs, pkg_paths, src_path, dst_path)
dst_paths.append(dst_path)
# Now copy everything back, overwriting the original code
for dst_path, src_path in zip(dst_paths, sv_paths):
shutil.copy(dst_path, src_path)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('file_list',
help=('File containing a list of '
'paths on which to work.'))
parser.add_argument('--verbose', '-v', action='store_true',
help="Log messages about what we're doing.")
parser.add_argument('--define', '-D', action='append', dest='defines',
default=[],
help='Add a preprocessor define.')
parser.add_argument('--define-if', action='append',
dest='defines_if', type=parse_define_if, default=[],
help=('Add a preprocessor define which applies to '
'specific files. For example '
'--define-if=foo:bar would define `bar on any '
'files whose paths contained a match for the '
'regex "foo".'))
parser.add_argument('--incdir', '-I', action='append', dest='incdirs',
default=[],
help='Add an include dir for the preprocessor.')
parser.add_argument('--incdir-list',
help=('Specify a file containing a list of include '
'directories (which are appended to any defined '
'through the --incdir argument).'))
parser.add_argument('--sv2v',
default='sv2v',
help=("Specify the name or path of the sv2v binary. "
"Defaults to 'sv2v'."))
args = parser.parse_args()
if args.verbose:
logging.basicConfig(level=logging.INFO)
try:
logging.info('Reading file list from {!r}.'.format(args.file_list))
paths = read_file_list(args.file_list)
except IOError:
logging.error('Failed to read file list from {!r}'
.format(args.file_list))
return 1
if args.incdir_list is not None:
try:
logging.info('Reading incdir list from {!r}.'
.format(args.incdir_list))
args.incdirs += read_file_list(args.incdir_list)
except IOError:
logging.error('Failed to read incdir list from {!r}'
.format(args.file_list))
return 1
# Find all .sv or .svh files, splitting out paths ending in "pkg.sv"
# specially. We treat these as packages, which are included in each sv2v
# conversion.
sv_paths = []
svh_paths = []
pkg_paths = []
for path in paths:
if os.path.splitext(path)[1] == '.sv':
sv_paths.append(path)
if os.path.splitext(path)[1] == '.svh':
svh_paths.append(path)
if path.endswith('pkg.sv'):
pkg_paths.append(path)
logging.info('Running sv2v in-place on {} files ({} packages).'
.format(len(sv_paths), len(pkg_paths)))
try:
transform(args.sv2v, args.defines, args.defines_if, args.incdirs,
pkg_paths, sv_paths)
except RuntimeError as err:
logging.error(err)
return 1
# Empty out any remaining .svh files: they should have been included by
# this point (sv2v includes a preprocessor).
logging.info('Splatting contents of {} .svh files.'.format(len(svh_paths)))
for path in svh_paths:
with open(path, 'w'):
pass
return 0
if __name__ == '__main__':
exit(main())