rpc: add support for Yosys RPC protocol.
This commit is contained in:
parent
1621ceb65a
commit
52f36025a9
112
nmigen/rpc.py
Normal file
112
nmigen/rpc.py
Normal file
|
@ -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()
|
5
setup.py
5
setup.py
|
@ -23,6 +23,11 @@ setup(
|
||||||
setup_requires=["setuptools_scm"],
|
setup_requires=["setuptools_scm"],
|
||||||
install_requires=["setuptools", "pyvcd>=0.1.4", "bitarray", "Jinja2"],
|
install_requires=["setuptools", "pyvcd>=0.1.4", "bitarray", "Jinja2"],
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
|
entry_points={
|
||||||
|
"console_scripts": [
|
||||||
|
"nmigen-rpc = nmigen.rpc:main",
|
||||||
|
]
|
||||||
|
},
|
||||||
project_urls={
|
project_urls={
|
||||||
#"Documentation": "https://nmigen.readthedocs.io/",
|
#"Documentation": "https://nmigen.readthedocs.io/",
|
||||||
"Source Code": "https://github.com/m-labs/nmigen",
|
"Source Code": "https://github.com/m-labs/nmigen",
|
||||||
|
|
Loading…
Reference in a new issue