-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
rpc: add support for experimental Yosys RPC protocol.
whitequark
committed
Sep 27, 2019
1 parent
e3a1d05
commit acbd09c
Showing
2 changed files
with
117 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters