From: whitequark Date: Fri, 27 Sep 2019 02:35:45 +0000 (+0000) Subject: rpc: add support for Yosys RPC protocol. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=7c8aeee7476412cb0c98e9d81d106b9053a30380;p=nmigen.git rpc: add support for Yosys RPC protocol. --- diff --git a/nmigen/rpc.py b/nmigen/rpc.py new file mode 100644 index 0000000..ac19395 --- /dev/null +++ b/nmigen/rpc.py @@ -0,0 +1,112 @@ +import sys +import json +import argparse +import importlib + +from .hdl import Signal, Elaboratable +from .back import rtlil + + +__all__ = ["main"] + + +def _collect_modules(names): + modules = {} + for name in names: + py_module_name, py_class_name = name.rsplit(".", 1) + py_module = importlib.import_module(py_module_name) + if py_class_name == "*": + for py_class_name in py_module.__all__: + py_class = py_module.__dict__[py_class_name] + if not issubclass(py_class, Elaboratable): + continue + modules["{}.{}".format(py_module_name, py_class_name)] = py_class + else: + py_class = py_module.__dict__[py_class_name] + if not isinstance(py_class, type) or not issubclass(py_class, Elaboratable): + raise TypeError("{}.{} is not a class inheriting from Elaboratable" + .format(py_module_name, py_class_name)) + modules[name] = py_class + return modules + + +def _serve_yosys(modules): + while True: + request_json = sys.stdin.readline() + if not request_json: break + request = json.loads(request_json) + + if request["method"] == "modules": + response = {"modules": list(modules.keys())} + + elif request["method"] == "derive": + module_name = request["module"] + + args, kwargs = [], {} + for parameter_name, parameter in request["parameters"].items(): + if parameter["type"] == "unsigned": + parameter_value = int(parameter["value"], 2) + elif parameter["type"] == "signed": + width = len(parameter["value"]) + parameter_value = int(parameter["value"], 2) + if parameter_value & (1 << (width - 1)): + parameter_value = -((1 << width) - value) + elif parameter["type"] == "string": + parameter_value = parameter["value"] + elif parameter["type"] == "real": + parameter_value = float(parameter["value"]) + else: + raise NotImplementedError("Unrecognized parameter type {}" + .format(parameter_name)) + if parameter_name.startswith("$"): + index = int(parameter_name[1:]) + while len(args) < index: + args.append(None) + args[index] = parameter_value + if parameter_name.startswith("\\"): + kwargs[parameter_name[1:]] = parameter_value + + try: + elaboratable = modules[module_name](*args, **kwargs) + def has_port(elaboratable, port_name): + # By convention, any public attribute that is a Signal is considered a port. + return (not port_name.startswith("_") and + isinstance(getattr(elaboratable, port_name), Signal)) + ports = [getattr(elaboratable, port_name) + for port_name in dir(elaboratable) + if has_port(elaboratable, port_name)] + rtlil_text = rtlil.convert(elaboratable, name=module_name, ports=ports) + response = {"frontend": "ilang", "source": rtlil_text} + except Exception as error: + response = {"error": "{}: {}".format(type(error).__name__, str(error))} + + else: + return {"error": "Unrecognized method {!r}".format(request["method"])} + + sys.stdout.write(json.dumps(response)) + sys.stdout.write("\n") + sys.stdout.flush() + + +def main(): + parser = argparse.ArgumentParser(description=r""" + The nMigen RPC server allows a HDL synthesis program to request an nMigen module to + be elaborated on demand using the parameters it provides. For example, using Yosys together + with the nMigen RPC server allows instantiating parametric nMigen modules directly + from Verilog. + """) + def add_modules_arg(parser): + parser.add_argument("modules", metavar="MODULE", type=str, nargs="+", + help="import and provide MODULES") + protocols = parser.add_subparsers(metavar="PROTOCOL", dest="protocol", required=True) + protocol_yosys = protocols.add_parser("yosys", help="use Yosys JSON-based RPC protocol") + add_modules_arg(protocol_yosys) + + args = parser.parse_args() + modules = _collect_modules(args.modules) + if args.protocol == "yosys": + _serve_yosys(modules) + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index b3da5d2..390e007 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,11 @@ setup( setup_requires=["setuptools_scm"], install_requires=["setuptools", "pyvcd>=0.1.4", "bitarray", "Jinja2"], packages=find_packages(), + entry_points={ + "console_scripts": [ + "nmigen-rpc = nmigen.rpc:main", + ] + }, project_urls={ #"Documentation": "https://nmigen.readthedocs.io/", "Source Code": "https://github.com/m-labs/nmigen",