f4pga/f4pga/sf_common.py

266 lines
8.1 KiB
Python

from argparse import Namespace
import subprocess
import os
import shutil
import sys
import re
def decompose_depname(name: str):
spec = 'req'
specchar = name[len(name) - 1]
if specchar == '?':
spec = 'maybe'
elif specchar == '!':
spec = 'demand'
if spec != 'req':
name = name[:len(name) - 1]
return name, spec
def with_qualifier(name: str, q: str) -> str:
if q == 'req':
return decompose_depname(name)[0]
if q == 'maybe':
return decompose_depname(name)[0] + '?'
if q == 'demand':
return decompose_depname(name)[0] + '!'
_sfbuild_module_collection_name_to_path = {}
def scan_modules(mypath: str):
global _sfbuild_module_collection_name_to_path
sfbuild_home = mypath
sfbuild_home_dirs = os.listdir(sfbuild_home)
sfbuild_module_dirs = \
[dir for dir in sfbuild_home_dirs if re.match('sf_.*_modules$', dir)]
_sfbuild_module_collection_name_to_path = \
dict([(re.match('sf_(.*)_modules$', moddir).groups()[0],
os.path.join(sfbuild_home, moddir))
for moddir in sfbuild_module_dirs])
"""Resolves module location from modulestr"""
def resolve_modstr(modstr: str):
sl = modstr.split(':')
if len(sl) > 2:
raise Exception('Incorrect module sysntax. '
'Expected one \':\' or one \'::\'')
if len(sl) < 2:
return modstr
collection_name = sl[0]
module_filename = sl[1] + '.py'
col_path = _sfbuild_module_collection_name_to_path.get(collection_name)
if not col_path:
fatal(-1, f'Module collection {collection_name} does not exist')
return os.path.join(col_path, module_filename)
def deep(fun):
"""
Create a recursive string transform function for 'str | list | dict',
i.e a dependency
"""
def d(paths, *args, **kwargs):
if type(paths) is str:
return fun(paths)
elif type(paths) is list:
return [d(p) for p in paths];
elif type(paths) is dict:
return dict([(k, d(p)) for k, p in paths.items()])
return d
def file_noext(path: str):
""" Return a file without it's extenstion"""
m = re.match('(.*)\\.[^.]*$', path)
if m:
path = m.groups()[0]
return path
class VprArgs:
""" Represents argument list for VPR (Versatile Place and Route) """
arch_dir: str
arch_def: str
lookahead: str
rr_graph: str
place_delay: str
device_name: str
eblif: str
optional: list
def __init__(self, share: str, eblif, values: Namespace,
sdc_file: 'str | None' = None,
vpr_extra_opts: 'list | None' = None):
self.arch_dir = os.path.join(share, 'arch')
self.arch_def = values.arch_def
self.lookahead = values.rr_graph_lookahead_bin
self.rr_graph = values.rr_graph_real_bin
self.place_delay = values.vpr_place_delay
self.device_name = values.vpr_grid_layout_name
self.eblif = os.path.realpath(eblif)
if values.vpr_options is not None:
self.optional = options_dict_to_list(values.vpr_options)
else:
self.optional = []
if vpr_extra_opts is not None:
self.optional += vpr_extra_opts
if sdc_file is not None:
self.optional += ['--sdc_file', sdc_file]
class SubprocessException(Exception):
return_code: int
def sub(*args, env=None, cwd=None):
""" Execute subroutine """
out = subprocess.run(args, capture_output=True, env=env, cwd=cwd)
if out.returncode != 0:
print(f'[ERROR]: {args[0]} non-zero return code.\n'
f'stderr:\n{out.stderr.decode()}\n\n'
)
exit(out.returncode)
return out.stdout
def vpr(mode: str, vprargs: VprArgs, cwd=None):
""" Execute `vpr` """
modeargs = []
if mode == 'pack':
modeargs = ['--pack']
elif mode == 'place':
modeargs = ['--place']
elif mode == 'route':
modeargs = ['--route']
return sub(*(['vpr',
vprargs.arch_def,
vprargs.eblif,
'--device', vprargs.device_name,
'--read_rr_graph', vprargs.rr_graph,
'--read_router_lookahead', vprargs.lookahead,
'--read_placement_delay_lookup', vprargs.place_delay] +
modeargs + vprargs.optional),
cwd=cwd)
_vpr_specific_values = [
'arch_def',
'rr_graph_lookahead_bin',
'rr_graph_real_bin',
'vpr_place_delay',
'vpr_grid_layout_name',
'vpr_options?'
]
def vpr_specific_values():
global _vpr_specific_values
return _vpr_specific_values
def options_dict_to_list(opt_dict: dict):
"""
Converts a dictionary of named options for CLI program to a list.
Example: { "option_name": "value" } -> [ "--option_name", "value" ]
"""
opts = []
for key, val in opt_dict.items():
opts.append('--' + key)
if not(type(val) is list and val == []):
opts.append(str(val))
return opts
def noisy_warnings(device):
""" Emit some noisy warnings """
os.environ['OUR_NOISY_WARNINGS'] = 'noisy_warnings-' + device + '_pack.log'
def my_path():
""" Get current PWD """
mypath = os.path.realpath(sys.argv[0])
return os.path.dirname(mypath)
def save_vpr_log(filename, build_dir=''):
""" Save VPR logic (moves the default output file into a desired path) """
shutil.move(os.path.join(build_dir, 'vpr_stdout.log'), filename)
def fatal(code, message):
"""
Print a message informing about an error that has occured and terminate program
with a given return code.
"""
print(f'[FATAL ERROR]: {message}')
exit(code)
class ResolutionEnv:
"""
ResolutionEnv is used to hold onto mappings for variables used in flow and
perform text substitutions using those variables.
Variables can be referred in any "resolvable" string using the following
syntax: 'Some static text ${variable_name}'. The '${variable_name}' part
will be replaced by the value associated with name 'variable_name', is such
mapping exists.
values: dict
"""
def __init__(self, values={}):
self.values = values
def __copy__(self):
return ResolutionEnv(self.values.copy())
def resolve(self, s, final=False):
"""
Perform resolution on `s`.
`s` can be a `str`, a `dict` with arbitrary keys and resolvable values,
or a `list` of resolvable values.
final=True - resolve any unknown variables into ''
This is a hack and probably should be removed in the future
"""
if type(s) is str:
match_list = list(re.finditer('\$\{([^${}]*)\}', s))
# Assumption: re.finditer finds matches in a left-to-right order
match_list.reverse()
for match in match_list:
match_str = match.group(1)
match_str = match_str.replace('?', '')
v = self.values.get(match_str)
if not v:
if final:
v = ''
else:
continue
span = match.span()
if type(v) is str:
s = s[:span[0]] + v + s[span[1]:]
elif type(v) is list: # Assume it's a list of strings
ns = list([s[:span[0]] + ve + s[span[1]:] for ve in v])
s = ns
elif type(s) is list:
s = list(map(self.resolve, s))
elif type(s) is dict:
s = dict([(k, self.resolve(v)) for k, v in s.items()])
return s
def add_values(self, values: dict):
""" Add mappings from `values`"""
for k, v in values.items():
self.values[k] = self.resolve(v)
verbosity_level = 0
def sfprint(verbosity: int, *args):
""" Print with regards to currently set verbosity level """
global verbosity_level
if verbosity <= verbosity_level:
print(*args)
def set_verbosity_level(level: int):
global verbosity_level
verbosity_level = level
def get_verbosity_level() -> int:
global verbosity_level
return verbosity_level