_yosys: add a way to retrieve Yosys data directory.
[nmigen.git] / nmigen / rpc.py
1 import sys
2 import json
3 import argparse
4 import importlib
5
6 from .hdl import Signal, Record, Elaboratable
7 from .back import rtlil
8
9
10 __all__ = ["main"]
11
12
13 def _collect_modules(names):
14 modules = {}
15 for name in names:
16 py_module_name, py_class_name = name.rsplit(".", 1)
17 py_module = importlib.import_module(py_module_name)
18 if py_class_name == "*":
19 for py_class_name in py_module.__all__:
20 py_class = py_module.__dict__[py_class_name]
21 if not issubclass(py_class, Elaboratable):
22 continue
23 modules["{}.{}".format(py_module_name, py_class_name)] = py_class
24 else:
25 py_class = py_module.__dict__[py_class_name]
26 if not isinstance(py_class, type) or not issubclass(py_class, Elaboratable):
27 raise TypeError("{}.{} is not a class inheriting from Elaboratable"
28 .format(py_module_name, py_class_name))
29 modules[name] = py_class
30 return modules
31
32
33 def _serve_yosys(modules):
34 while True:
35 request_json = sys.stdin.readline()
36 if not request_json: break
37 request = json.loads(request_json)
38
39 if request["method"] == "modules":
40 response = {"modules": list(modules.keys())}
41
42 elif request["method"] == "derive":
43 module_name = request["module"]
44
45 args, kwargs = [], {}
46 for parameter_name, parameter in request["parameters"].items():
47 if parameter["type"] == "unsigned":
48 parameter_value = int(parameter["value"], 2)
49 elif parameter["type"] == "signed":
50 width = len(parameter["value"])
51 parameter_value = int(parameter["value"], 2)
52 if parameter_value & (1 << (width - 1)):
53 parameter_value = -((1 << width) - value)
54 elif parameter["type"] == "string":
55 parameter_value = parameter["value"]
56 elif parameter["type"] == "real":
57 parameter_value = float(parameter["value"])
58 else:
59 raise NotImplementedError("Unrecognized parameter type {}"
60 .format(parameter_name))
61 if parameter_name.startswith("$"):
62 index = int(parameter_name[1:])
63 while len(args) < index:
64 args.append(None)
65 args[index] = parameter_value
66 if parameter_name.startswith("\\"):
67 kwargs[parameter_name[1:]] = parameter_value
68
69 try:
70 elaboratable = modules[module_name](*args, **kwargs)
71 ports = []
72 # By convention, any public attribute that is a Signal or a Record is
73 # considered a port.
74 for port_name, port in vars(elaboratable).items():
75 if not port_name.startswith("_") and isinstance(port, (Signal, Record)):
76 ports += port._lhs_signals()
77 rtlil_text = rtlil.convert(elaboratable, name=module_name, ports=ports)
78 response = {"frontend": "ilang", "source": rtlil_text}
79 except Exception as error:
80 response = {"error": "{}: {}".format(type(error).__name__, str(error))}
81
82 else:
83 return {"error": "Unrecognized method {!r}".format(request["method"])}
84
85 sys.stdout.write(json.dumps(response))
86 sys.stdout.write("\n")
87 sys.stdout.flush()
88
89
90 def main():
91 parser = argparse.ArgumentParser(description=r"""
92 The nMigen RPC server allows a HDL synthesis program to request an nMigen module to
93 be elaborated on demand using the parameters it provides. For example, using Yosys together
94 with the nMigen RPC server allows instantiating parametric nMigen modules directly
95 from Verilog.
96 """)
97 def add_modules_arg(parser):
98 parser.add_argument("modules", metavar="MODULE", type=str, nargs="+",
99 help="import and provide MODULES")
100 protocols = parser.add_subparsers(metavar="PROTOCOL", dest="protocol", required=True)
101 protocol_yosys = protocols.add_parser("yosys", help="use Yosys JSON-based RPC protocol")
102 add_modules_arg(protocol_yosys)
103
104 args = parser.parse_args()
105 modules = _collect_modules(args.modules)
106 if args.protocol == "yosys":
107 _serve_yosys(modules)
108
109
110 if __name__ == "__main__":
111 main()