Skip to content

Commit

Permalink
Connect flow API to Edalizer
Browse files Browse the repository at this point in the history
  • Loading branch information
olofk committed Oct 31, 2022
1 parent d6b9351 commit dccf850
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 30 deletions.
136 changes: 123 additions & 13 deletions fusesoc/edalizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, [path])


def str2bool(v):
if isinstance(v, bool):
return v
if v.lower() in ("yes", "true", "t", "y", "1"):
return True
elif v.lower() in ("no", "false", "f", "n", "0"):
return False
else:
raise argparse.ArgumentTypeError("Boolean value expected.")


class Edalizer:
def __init__(
self,
Expand Down Expand Up @@ -166,9 +177,13 @@ def create_edam(self):
merge_dict(parameters, snippet["parameters"])

# Extract tool options
snippet["tool_options"] = {
self.flags["tool"]: core.get_tool_options(_flags)
}
if self.flags.get("tool"):
snippet["tool_options"] = {
self.flags["tool"]: core.get_tool_options(_flags)
}

# Extract flow options
snippet["flow_options"] = core.get_flow_options(_flags)

# Extract scripts
snippet["hooks"] = core.get_scripts(rel_root, _flags)
Expand Down Expand Up @@ -252,7 +267,7 @@ def clean_temp_dirs(self):

def _build_parser(self, backend_class, edam):
typedict = {
"bool": {"action": "store_true"},
"bool": {"type": str2bool, "nargs": "?", "const": True},
"file": {"type": str, "nargs": 1, "action": FileAction},
"int": {"type": int, "nargs": 1},
"str": {"type": str, "nargs": 1},
Expand Down Expand Up @@ -313,20 +328,54 @@ def _build_parser(self, backend_class, edam):

# backend_args.
backend_args = parser.add_argument_group("Backend arguments")
_opts = backend_class.get_doc(0)

for _opt in _opts.get("members", []) + _opts.get("lists", []):
backend_args.add_argument("--" + _opt["name"], help=_opt["desc"])
if hasattr(backend_class, "get_flow_options"):
for k, v in backend_class.get_flow_options().items():
backend_args.add_argument(
"--" + k,
help=v["desc"],
**typedict[v["type"]],
)
for k, v in backend_class.get_tool_options(
self.activated_flow_options
).items():
backend_args.add_argument(
"--" + k,
help=v["desc"],
**typedict[v["type"]],
)
else:
_opts = backend_class.get_doc(0)
for _opt in _opts.get("members", []) + _opts.get("lists", []):
backend_args.add_argument("--" + _opt["name"], help=_opt["desc"])

return parser

def add_parsed_args(self, backend_class, parsed_args):
_opts = backend_class.get_doc(0)
# Parse arguments
backend_members = [x["name"] for x in _opts.get("members", [])]
backend_lists = [x["name"] for x in _opts.get("lists", [])]
if hasattr(backend_class, "get_flow_options"):
backend_members = []
backend_lists = []
for k, v in backend_class.get_flow_options().items():
if v.get("list"):
backend_lists.append(k)
else:
backend_members.append(k)
for k, v in backend_class.get_tool_options(
self.activated_flow_options
).items():
if v.get("list"):
backend_lists.append(k)
else:
backend_members.append(k)
tool_options = self.edam["flow_options"]
else:
_opts = backend_class.get_doc(0)
# Parse arguments
backend_members = [x["name"] for x in _opts.get("members", [])]
backend_lists = [x["name"] for x in _opts.get("lists", [])]

tool = backend_class.__name__.lower()
tool_options = self.edam["tool_options"][tool]
tool = backend_class.__name__.lower()
tool_options = self.edam["tool_options"][tool]

for key, value in sorted(parsed_args.items()):
if value is None:
Expand All @@ -343,7 +392,68 @@ def add_parsed_args(self, backend_class, parsed_args):
else:
raise RuntimeError("Unknown parameter " + key)

def _parse_flow_options(self, backend_class, backendargs, edam):
available_flow_options = backend_class.get_flow_options()

# First we check which flow options that are set in the EDAM.
# edam["flow_options"] contain both flow and tool options, so
# we only pick the former here
flow_options = {}
for k, v in edam["flow_options"].items():
if k in available_flow_options:
flow_options[k] = v

# Next we build a parser and use it to parse the command-line
progname = "fusesoc run {}".format(edam["name"])
parser = argparse.ArgumentParser(
prog=progname, conflict_handler="resolve", add_help=False
)
backend_args = parser.add_argument_group("Flow options")
typedict = {
"bool": {"type": str2bool, "nargs": "?", "const": True},
"file": {"type": str, "nargs": 1, "action": FileAction},
"int": {"type": int, "nargs": 1},
"str": {"type": str, "nargs": 1},
"real": {"type": float, "nargs": 1},
}
for k, v in available_flow_options.items():
backend_args.add_argument(
"--" + k,
help=v["desc"],
**typedict[v["type"]],
)

# Parse known args (i.e. only flow options) from the command-line
parsed_args = parser.parse_known_args(backendargs)[0]

# Clean up parsed arguments object and convert to dict
parsed_args_dict = {}
for key, value in sorted(vars(parsed_args).items()):
# Remove arguments with value None, i.e. arguments not encountered
# on the command line
if value is None:
continue
_value = value[0] if type(value) == list else value

# If flow option is a list, we split up the parsed string
if "list" in available_flow_options[key]:
_value = _value.split(" ")
parsed_args_dict[key] = _value

# Add parsed args to the ones from the EDAM
merge_dict(flow_options, parsed_args_dict)

return flow_options

def parse_args(self, backend_class, backendargs, edam):
# First we need to see which flow options are set,
# in order to know which tool options that are relevant
# for this configuration of the flow
if hasattr(backend_class, "get_flow_options"):
self.activated_flow_options = self._parse_flow_options(
backend_class, backendargs, edam
)

parser = self._build_parser(backend_class, edam)
parsed_args = parser.parse_args(backendargs)

Expand Down
54 changes: 38 additions & 16 deletions fusesoc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import subprocess
import sys
import warnings
from importlib import import_module

from fusesoc import __version__

Expand Down Expand Up @@ -331,44 +332,65 @@ def run_backend(
verbose,
):
tool_error = (
"No tool was supplied on command line or found in '{}' core description"
"No flow or tool was supplied on command line or found in '{}' core description"
)
core = _get_core(cm, system)

target = flags["target"]

flow = core.get_flow(flags)
try:
flags = dict(core.get_flags(target), **flags)
except SyntaxError as e:
logger.error(str(e))
exit(1)

tool = flags.get("tool")
if flow:
logger.debug(f"Using flow API (flow={flow})")
else:
logger.debug("flow not set. Falling back to tool API")
if "tool" in flags:
tool = flags["tool"]
else:
logger.error(tool_error.format(system))
exit(1)

if not tool:
logger.error(tool_error.format(system))
exit(1)
build_root = build_root_arg or os.path.join(
cm.config.build_root, core.name.sanitized_name
)
logger.debug(f"Setting build_root to {build_root}")

work_root = os.path.join(build_root, f"{target}-{flow or tool}")
logger.debug(f"Setting work_root to {work_root}")

if export:
export_root = os.path.join(build_root, "src")
logger.debug(f"Setting export_root to {export_root}")
else:
export_root = None
try:
work_root = os.path.join(build_root, f"{target}-{tool}")
except SyntaxError as e:
logger.error(e.msg)
exit(1)
edam_file = os.path.join(work_root, core.name.sanitized_name + ".eda.yml")
if not os.path.exists(edam_file):
do_configure = True

try:
backend_class = get_edatool(tool)
except ImportError:
logger.error(f"Backend {tool!r} not found")
exit(1)
backend_class = None
if flow:
try:
backend_class = getattr(
import_module(f"edalize.flows.{flow}"), flow.capitalize()
)
except ModuleNotFoundError:
logger.error(f"Flow {flow!r} not found")
exit(1)
except ImportError:
logger.error("Selected Edalize version does not support the flow API")
exit(1)

else:
try:
backend_class = get_edatool(tool)
except ImportError:
logger.error(f"Backend {tool!r} not found")
exit(1)

edalizer = Edalizer(
toplevel=core.name,
Expand Down Expand Up @@ -411,7 +433,7 @@ def run_backend(

if do_configure:
try:
backend.configure([])
backend.configure()
print("")
except RuntimeError as e:
logger.error("Failed to configure the system")
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def read(fname):
"setuptools_scm; python_version>='3.7'",
],
install_requires=[
"edalize>=0.2.3",
"edalize>=0.3.3",
"pyparsing",
"pyyaml",
"simplesat>=0.8.0",
Expand Down

0 comments on commit dccf850

Please sign in to comment.