266 lines
8.1 KiB
Python
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
|