From: whitequark Date: Thu, 26 Sep 2019 03:57:16 +0000 (+0000) Subject: rpc: new frontend. X-Git-Tag: working-ls180~1029^2 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=99a7f39084cf4b9cd21e2a1e4f4a842993dfd147;p=yosys.git rpc: new frontend. A new pass, connect_rpc, allows any HDL frontend that can read/write JSON from/to stdin/stdout or an unix socket or a named pipe to participate in elaboration as a first class citizen, such that any other HDL supported by Yosys directly or indirectly can transparently instantiate modules handled by this frontend. Recognizing that many HDL frontends emit Verilog, it allows the RPC frontend to direct Yosys to process the result of instantiation via any built-in Yosys frontend. The resulting RTLIL is then hygienically integrated into the overall design. --- diff --git a/Makefile b/Makefile index 2bd6b218f..c36aef83f 100644 --- a/Makefile +++ b/Makefile @@ -713,6 +713,7 @@ test: $(TARGETS) $(EXTRA_TARGETS) +cd tests/aiger && bash run-test.sh $(ABCOPT) +cd tests/arch && bash run-test.sh +cd tests/ice40 && bash run-test.sh $(SEEDOPT) + +cd tests/rpc && bash run-test.sh @echo "" @echo " Passed \"make test\"." @echo "" diff --git a/frontends/rpc/Makefile.inc b/frontends/rpc/Makefile.inc new file mode 100644 index 000000000..9af505098 --- /dev/null +++ b/frontends/rpc/Makefile.inc @@ -0,0 +1,2 @@ + +OBJS += frontends/rpc/rpc_frontend.o diff --git a/frontends/rpc/rpc_frontend.cc b/frontends/rpc/rpc_frontend.cc new file mode 100644 index 000000000..b4b2fa3a2 --- /dev/null +++ b/frontends/rpc/rpc_frontend.cc @@ -0,0 +1,589 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2019 whitequark + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +// The reason the -path mode of connect_rpc uses byte-oriented and not message-oriented sockets, even though +// it is a message-oriented interface, is that the system can place various limits on the message size, which +// are not always transparent or easy to change. Given that generated HDL code get be extremely large, it is +// unwise to rely on those limits being large enough, and using byte-oriented sockets is guaranteed to work. + +#ifndef _WIN32 +#include +#include +#include +#include +#include +#endif + +#include "libs/json11/json11.hpp" +#include "libs/sha1/sha1.h" +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +#if defined(_WIN32) +static std::wstring str2wstr(const std::string &in) { + if(in == "") return L""; + std::wstring out; + out.resize(MultiByteToWideChar(/*CodePage=*/CP_UTF8, /*dwFlags=*/0, /*lpMultiByteStr=*/&in[0], /*cbMultiByte=*/(int)in.length(), /*lpWideCharStr=*/NULL, /*cchWideChar=*/0)); + int written = MultiByteToWideChar(/*CodePage=*/CP_UTF8, /*dwFlags=*/0, /*lpMultiByteStr=*/&in[0], /*cbMultiByte=*/(int)in.length(), /*lpWideCharStr=*/&out[0], /*cchWideChar=*/(int)out.length()); + log_assert(written == (int)out.length()); + return out; +} + +static std::string wstr2str(const std::wstring &in) { + if(in == L"") return ""; + std::string out; + out.resize(WideCharToMultiByte(/*CodePage=*/CP_UTF8, /*dwFlags=*/0, /*lpWideCharStr=*/&in[0], /*cchWideChar=*/(int)in.length(), /*lpMultiByteStr=*/NULL, /*cbMultiByte=*/0, /*lpDefaultChar=*/NULL, /*lpUsedDefaultChar=*/NULL)); + int written = WideCharToMultiByte(/*CodePage=*/CP_UTF8, /*dwFlags=*/0, /*lpWideCharStr=*/&in[0], /*cchWideChar=*/(int)in.length(), /*lpMultiByteStr=*/&out[0], /*cbMultiByte=*/(int)out.length(), /*lpDefaultChar=*/NULL, /*lpUsedDefaultChar=*/NULL); + log_assert(written == (int)out.length()); + return out; +} + +static std::string get_last_error_str() { + DWORD last_error = GetLastError(); + LPWSTR out_w; + DWORD size_w = FormatMessageW(/*dwFlags=*/FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_IGNORE_INSERTS, /*lpSource=*/NULL, /*dwMessageId=*/last_error, /*dwLanguageId=*/0, /*lpBuffer=*/(LPWSTR)&out_w, /*nSize=*/0, /*Arguments=*/NULL); + if (size_w == 0) + return std::to_string(last_error); + std::string out = wstr2str(std::wstring(out_w, size_w)); + LocalFree(out_w); + return out; +} +#endif + +using json11::Json; + +struct RpcServer { + std::string name; + + RpcServer(const std::string &name) : name(name) { } + virtual ~RpcServer() { } + + virtual void write(const std::string &data) = 0; + virtual std::string read() = 0; + + Json call(const Json &json_request) { + std::string request; + json_request.dump(request); + request += '\n'; + log_debug("RPC frontend request: %s", request.c_str()); + write(request); + + std::string response = read(); + log_debug("RPC frontend response: %s", response.c_str()); + std::string error; + Json json_response = Json::parse(response, error); + if (json_response.is_null()) + log_cmd_error("parsing JSON failed: %s\n", error.c_str()); + if (json_response["error"].is_string()) + log_cmd_error("RPC frontend returned an error: %s\n", json_response["error"].string_value().c_str()); + return json_response; + } + + std::vector get_module_names() { + Json response = call(Json::object { + { "method", "modules" }, + }); + bool is_valid = true; + std::vector modules; + if (response["modules"].is_array()) { + for (auto &json_module : response["modules"].array_items()) { + if (json_module.is_string()) + modules.push_back(json_module.string_value()); + else is_valid = false; + } + } else is_valid = false; + if (!is_valid) + log_cmd_error("RPC frontend returned malformed response: %s\n", response.dump().c_str()); + return modules; + } + + std::pair derive_module(const std::string &module, const dict ¶meters) { + Json::object json_parameters; + for (auto ¶m : parameters) { + std::string type, value; + if (param.second.flags & RTLIL::CONST_FLAG_REAL) { + type = "real"; + value = param.second.decode_string(); + } else if (param.second.flags & RTLIL::CONST_FLAG_STRING) { + type = "string"; + value = param.second.decode_string(); + } else if ((param.second.flags & ~RTLIL::CONST_FLAG_SIGNED) == RTLIL::CONST_FLAG_NONE) { + type = (param.second.flags & RTLIL::CONST_FLAG_SIGNED) ? "signed" : "unsigned"; + value = param.second.as_string(); + } else + log_cmd_error("Unserializable constant flags 0x%x\n", param.second.flags); + json_parameters[param.first.str()] = Json::object { + { "type", type }, + { "value", value }, + }; + } + Json response = call(Json::object { + { "method", "derive" }, + { "module", module }, + { "parameters", json_parameters }, + }); + bool is_valid = true; + std::string frontend, source; + if (response["frontend"].is_string()) + frontend = response["frontend"].string_value(); + else is_valid = false; + if (response["source"].is_string()) + source = response["source"].string_value(); + else is_valid = false; + if (!is_valid) + log_cmd_error("RPC frontend returned malformed response: %s\n", response.dump().c_str()); + return std::make_pair(frontend, source); + } +}; + +struct RpcModule : RTLIL::Module { + std::shared_ptr server; + + RTLIL::IdString derive(RTLIL::Design *design, dict parameters, bool /*mayfail*/) YS_OVERRIDE { + std::string stripped_name = name.str(); + if (stripped_name.compare(0, 9, "$abstract") == 0) + stripped_name = stripped_name.substr(9); + log_assert(stripped_name[0] == '\\'); + + log_header(design, "Executing RPC frontend `%s' for module `%s'.\n", server->name.c_str(), stripped_name.c_str()); + + std::string parameter_info; + for (auto ¶m : parameters) { + log("Parameter %s = %s\n", param.first.c_str(), log_signal(RTLIL::SigSpec(param.second))); + parameter_info += stringf("%s=%s", param.first.c_str(), log_signal(RTLIL::SigSpec(param.second))); + } + + std::string derived_name; + if (parameters.empty()) + derived_name = stripped_name; + else if (parameter_info.size() > 60) + derived_name = "$paramod$" + sha1(parameter_info) + stripped_name; + else + derived_name = "$paramod" + stripped_name + parameter_info; + + if (design->has(derived_name)) { + log("Found cached RTLIL representation for module `%s'.\n", derived_name.c_str()); + } else { + std::string command, input; + std::tie(command, input) = server->derive_module(stripped_name.substr(1), parameters); + + std::istringstream input_stream(input); + RTLIL::Design *derived_design = new RTLIL::Design; + Frontend::frontend_call(derived_design, &input_stream, "" + derived_name.substr(8), command); + derived_design->check(); + + dict name_mangling; + bool found_derived_top = false; + for (auto module : derived_design->modules()) { + std::string original_name = module->name.str(); + if (original_name == stripped_name) { + found_derived_top = true; + name_mangling[original_name] = derived_name; + } else { + name_mangling[original_name] = derived_name + module->name.str(); + } + } + if (!found_derived_top) + log_cmd_error("RPC frontend did not return requested module `%s`!\n", stripped_name.c_str()); + + for (auto module : derived_design->modules()) + for (auto cell : module->cells()) + if (name_mangling.count(cell->type.str())) + cell->type = name_mangling[cell->type.str()]; + + for (auto module : derived_design->modules_) { + std::string mangled_name = name_mangling[module.first.str()]; + + log("Importing `%s' as `%s'.\n", log_id(module.first), log_id(mangled_name)); + + module.second->name = mangled_name; + module.second->design = design; + module.second->attributes.erase("\\top"); + design->modules_[mangled_name] = module.second; + derived_design->modules_.erase(module.first); + } + + delete derived_design; + } + + return derived_name; + } + + RTLIL::Module *clone() const YS_OVERRIDE { + RpcModule *new_mod = new RpcModule; + new_mod->server = server; + cloneInto(new_mod); + return new_mod; + } +}; + +#if defined(_WIN32) + +struct HandleRpcServer : RpcServer { + HANDLE hsend, hrecv; + + HandleRpcServer(const std::string &name, HANDLE hsend, HANDLE hrecv) + : RpcServer(name), hsend(hsend), hrecv(hrecv) { } + + void write(const std::string &data) YS_OVERRIDE { + log_assert(data.length() >= 1 && data.find('\n') == data.length() - 1); + ssize_t offset = 0; + do { + DWORD data_written; + if (!WriteFile(hsend, &data[offset], data.length() - offset, &data_written, /*lpOverlapped=*/NULL)) + log_cmd_error("WriteFile failed: %s\n", get_last_error_str().c_str()); + offset += data_written; + } while(offset < (ssize_t)data.length()); + } + + std::string read() YS_OVERRIDE { + std::string data; + ssize_t offset = 0; + while (data.length() == 0 || data[data.length() - 1] != '\n') { + data.resize(data.length() + 1024); + DWORD data_read; + if (!ReadFile(hrecv, &data[offset], data.length() - offset, &data_read, /*lpOverlapped=*/NULL)) + log_cmd_error("ReadFile failed: %s\n", get_last_error_str().c_str()); + offset += data_read; + data.resize(offset); + size_t term_pos = data.find('\n', offset); + if (term_pos != data.length() - 1 && term_pos != std::string::npos) + log_cmd_error("read failed: more than one response\n"); + } + return data; + } + + ~HandleRpcServer() { + CloseHandle(hsend); + if (hrecv != hsend) + CloseHandle(hrecv); + } +}; + +#else + +struct FdRpcServer : RpcServer { + int fdsend, fdrecv; + pid_t pid; + + FdRpcServer(const std::string &name, int fdsend, int fdrecv, pid_t pid = -1) + : RpcServer(name), fdsend(fdsend), fdrecv(fdrecv), pid(pid) { } + + void check_pid() { + if (pid == -1) return; + // If we're communicating with a process, check that it's still running, or we may get killed with SIGPIPE. + pid_t wait_result = ::waitpid(pid, NULL, WNOHANG); + if (wait_result == -1) + log_cmd_error("waitpid failed: %s\n", strerror(errno)); + if (wait_result == pid) + log_cmd_error("RPC frontend terminated unexpectedly\n"); + } + + void write(const std::string &data) YS_OVERRIDE { + log_assert(data.length() >= 1 && data.find('\n') == data.length() - 1); + ssize_t offset = 0; + do { + check_pid(); + ssize_t result = ::write(fdsend, &data[offset], data.length() - offset); + if (result == -1) + log_cmd_error("write failed: %s\n", strerror(errno)); + offset += result; + } while(offset < (ssize_t)data.length()); + } + + std::string read() YS_OVERRIDE { + std::string data; + ssize_t offset = 0; + while (data.length() == 0 || data[data.length() - 1] != '\n') { + data.resize(data.length() + 1024); + check_pid(); + ssize_t result = ::read(fdrecv, &data[offset], data.length() - offset); + if (result == -1) + log_cmd_error("read failed: %s\n", strerror(errno)); + offset += result; + data.resize(offset); + size_t term_pos = data.find('\n', offset); + if (term_pos != data.length() - 1 && term_pos != std::string::npos) + log_cmd_error("read failed: more than one response\n"); + } + return data; + } + + ~FdRpcServer() { + close(fdsend); + if (fdrecv != fdsend) + close(fdrecv); + } +}; + +#endif + +// RpcFrontend does not inherit from Frontend since it does not read files. +struct RpcFrontend : public Pass { + RpcFrontend() : Pass("connect_rpc", "connect to RPC frontend") { } + void help() YS_OVERRIDE + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" connect_rpc -exec [args...]\n"); + log(" connect_rpc -path \n"); + log("\n"); + log("Load modules using an out-of-process frontend.\n"); + log("\n"); + log(" -exec [args...]\n"); + log(" run with arguments [args...]. send requests on stdin, read\n"); + log(" responses from stdout.\n"); + log("\n"); + log(" -path \n"); + log(" connect to Unix domain socket at . (Unix)\n"); + log(" connect to bidirectional byte-type named pipe at . (Windows)\n"); + log("\n"); + log("A simple JSON-based, newline-delimited protocol is used for communicating with\n"); + log("the frontend. Yosys requests data from the frontend by sending exactly 1 line\n"); + log("of JSON. Frontend responds with data or error message by replying with exactly\n"); + log("1 line of JSON as well.\n"); + log("\n"); + log(" -> {\"method\": \"modules\"}\n"); + log(" <- {\"modules\": [\"\", ...]}\n"); + log(" <- {\"error\": \"\"}\n"); + log(" request for the list of modules that can be derived by this frontend.\n"); + log(" the 'hierarchy' command will call back into this frontend if a cell\n"); + log(" with type is instantiated in the design.\n"); + log("\n"); + log(" -> {\"method\": \"derive\", \"module\": \", \"parameters\": {\n"); + log(" \"\": {\"type\": \"[unsigned|signed|string|real]\",\n"); + log(" \"value\": \"\"}, ...}}\n"); + log(" <- {\"frontend\": \"[ilang|verilog|...]\",\"source\": \"\"}}\n"); + log(" <- {\"error\": \"\"}\n"); + log(" request for the module to be derived for a specific set of\n"); + log(" parameters. starts with \\ for named parameters, and with $\n"); + log(" for unnamed parameters, which are numbered starting at 1.\n"); + log(" for integer parameters is always specified as a binary string of unlimited\n"); + log(" precision. the returned by the frontend is hygienically parsed\n"); + log(" by a built-in Yosys , allowing the RPC frontend to return any\n"); + log(" convenient representation of the module. the derived module is cached,\n"); + log(" so the response should be the same whenever the same set of parameters\n"); + log(" is provided.\n"); + } + void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE + { + log_header(design, "Connecting to RPC frontend.\n"); + + std::vector command; + std::string path; + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + std::string arg = args[argidx]; + if (arg == "-exec" && argidx+1 < args.size()) { + command.insert(command.begin(), args.begin() + argidx + 1, args.end()); + continue; + } + if (arg == "-path" && argidx+1 < args.size()) { + path = args[argidx+1]; + continue; + } + break; + } + extra_args(args, argidx, design); + + if ((!command.empty()) + (!path.empty()) != 1) + log_cmd_error("Exactly one of -exec, -unix must be specified.\n"); + + std::shared_ptr server; + if (!command.empty()) { + std::string command_line; + bool first = true; + for (auto &arg : command) { + if (!first) command_line += ' '; + command_line += arg; + first = false; + } + +#ifdef _WIN32 + std::wstring command_w = str2wstr(command[0]); + std::wstring command_path_w; + std::wstring command_line_w = str2wstr(command_line); + DWORD command_path_len_w; + SECURITY_ATTRIBUTES pipe_attr = {}; + HANDLE send_r = NULL, send_w = NULL, recv_r = NULL, recv_w = NULL; + STARTUPINFOW startup_info = {}; + PROCESS_INFORMATION proc_info = {}; + + command_path_len_w = SearchPathW(/*lpPath=*/NULL, /*lpFileName=*/command_w.c_str(), /*lpExtension=*/L".exe", /*nBufferLength=*/0, /*lpBuffer=*/NULL, /*lpFilePart=*/NULL); + if (command_path_len_w == 0) { + log_error("SearchPathW failed: %s\n", get_last_error_str().c_str()); + goto cleanup_exec; + } + command_path_w.resize(command_path_len_w - 1); + command_path_len_w = SearchPathW(/*lpPath=*/NULL, /*lpFileName=*/command_w.c_str(), /*lpExtension=*/L".exe", /*nBufferLength=*/command_path_len_w, /*lpBuffer=*/&command_path_w[0], /*lpFilePart=*/NULL); + log_assert(command_path_len_w == command_path_w.length()); + + pipe_attr.nLength = sizeof(pipe_attr); + pipe_attr.bInheritHandle = TRUE; + pipe_attr.lpSecurityDescriptor = NULL; + if (!CreatePipe(&send_r, &send_w, &pipe_attr, /*nSize=*/0)) { + log_error("CreatePipe failed: %s\n", get_last_error_str().c_str()); + goto cleanup_exec; + } + if (!SetHandleInformation(send_w, HANDLE_FLAG_INHERIT, 0)) { + log_error("SetHandleInformation failed: %s\n", get_last_error_str().c_str()); + goto cleanup_exec; + } + if (!CreatePipe(&recv_r, &recv_w, &pipe_attr, /*nSize=*/0)) { + log_error("CreatePipe failed: %s\n", get_last_error_str().c_str()); + goto cleanup_exec; + } + if (!SetHandleInformation(recv_r, HANDLE_FLAG_INHERIT, 0)) { + log_error("SetHandleInformation failed: %s\n", get_last_error_str().c_str()); + goto cleanup_exec; + } + + startup_info.cb = sizeof(startup_info); + startup_info.hStdInput = send_r; + startup_info.hStdOutput = recv_w; + startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + startup_info.dwFlags |= STARTF_USESTDHANDLES; + if (!CreateProcessW(/*lpApplicationName=*/command_path_w.c_str(), /*lpCommandLine=*/&command_line_w[0], /*lpProcessAttributes=*/NULL, /*lpThreadAttributes=*/NULL, /*bInheritHandles=*/TRUE, /*dwCreationFlags=*/0, /*lpEnvironment=*/NULL, /*lpCurrentDirectory=*/NULL, &startup_info, &proc_info)) { + log_error("CreateProcessW failed: %s\n", get_last_error_str().c_str()); + goto cleanup_exec; + } + CloseHandle(proc_info.hProcess); + CloseHandle(proc_info.hThread); + + server = std::make_shared(path, send_w, recv_r); + send_w = NULL; + recv_r = NULL; + +cleanup_exec: + if (send_r != NULL) CloseHandle(send_r); + if (send_w != NULL) CloseHandle(send_w); + if (recv_r != NULL) CloseHandle(recv_r); + if (recv_w != NULL) CloseHandle(recv_w); +#else + std::vector argv; + int send[2] = {-1,-1}, recv[2] = {-1,-1}; + posix_spawn_file_actions_t file_actions, *file_actions_p = NULL; + pid_t pid; + + for (auto &arg : command) + argv.push_back(&arg[0]); + argv.push_back(nullptr); + + if (pipe(send) != 0) { + log_error("pipe failed: %s\n", strerror(errno)); + goto cleanup_exec; + } + if (pipe(recv) != 0) { + log_error("pipe failed: %s\n", strerror(errno)); + goto cleanup_exec; + } + + if (posix_spawn_file_actions_init(&file_actions) != 0) { + log_error("posix_spawn_file_actions_init failed: %s\n", strerror(errno)); + goto cleanup_exec; + } + file_actions_p = &file_actions; + if (posix_spawn_file_actions_adddup2(file_actions_p, send[0], STDIN_FILENO) != 0) { + log_error("posix_spawn_file_actions_adddup2 failed: %s\n", strerror(errno)); + goto cleanup_exec; + } + if (posix_spawn_file_actions_addclose(file_actions_p, send[1]) != 0) { + log_error("posix_spawn_file_actions_addclose failed: %s\n", strerror(errno)); + goto cleanup_exec; + } + if (posix_spawn_file_actions_adddup2(file_actions_p, recv[1], STDOUT_FILENO) != 0) { + log_error("posix_spawn_file_actions_adddup2 failed: %s\n", strerror(errno)); + goto cleanup_exec; + } + if (posix_spawn_file_actions_addclose(file_actions_p, recv[0]) != 0) { + log_error("posix_spawn_file_actions_addclose failed: %s\n", strerror(errno)); + goto cleanup_exec; + } + + if (posix_spawnp(&pid, argv[0], file_actions_p, /*attrp=*/NULL, argv.data(), environ) != 0) { + log_error("posix_spawnp failed: %s\n", strerror(errno)); + goto cleanup_exec; + } + + server = std::make_shared(command_line, send[1], recv[0], pid); + send[1] = -1; + recv[0] = -1; + +cleanup_exec: + if (send[0] != -1) close(send[0]); + if (send[1] != -1) close(send[1]); + if (recv[0] != -1) close(recv[0]); + if (recv[1] != -1) close(recv[1]); + if (file_actions_p != NULL) + posix_spawn_file_actions_destroy(file_actions_p); +#endif + } else if (!path.empty()) { +#ifdef _WIN32 + std::wstring path_w = str2wstr(path); + HANDLE h; + + h = CreateFileW(path_w.c_str(), GENERIC_READ|GENERIC_WRITE, /*dwShareMode=*/0, /*lpSecurityAttributes=*/NULL, /*dwCreationDisposition=*/OPEN_EXISTING, /*dwFlagsAndAttributes=*/0, /*hTemplateFile=*/NULL); + if (h == INVALID_HANDLE_VALUE) { + log_error("CreateFileW failed: %s\n", get_last_error_str().c_str()); + goto cleanup_path; + } + + server = std::make_shared(path, h, h); + +cleanup_path: + ; +#else + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1); + + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + log_error("socket failed: %s\n", strerror(errno)); + goto cleanup_path; + } + + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + log_error("connect failed: %s\n", strerror(errno)); + goto cleanup_path; + } + + server = std::make_shared(path, fd, fd); + fd = -1; + +cleanup_path: + if (fd != -1) close(fd); +#endif + } + + if (!server) + log_cmd_error("Failed to connect to RPC frontend.\n"); + + for (auto &module_name : server->get_module_names()) { + log("Linking module `%s'.\n", module_name.c_str()); + RpcModule *module = new RpcModule; + module->name = "$abstract\\" + module_name; + module->server = server; + design->add(module); + } + } +} RpcFrontend; + +YOSYS_NAMESPACE_END diff --git a/tests/rpc/.gitignore b/tests/rpc/.gitignore new file mode 100644 index 000000000..397b4a762 --- /dev/null +++ b/tests/rpc/.gitignore @@ -0,0 +1 @@ +*.log diff --git a/tests/rpc/design.v b/tests/rpc/design.v new file mode 100644 index 000000000..80f1dac1a --- /dev/null +++ b/tests/rpc/design.v @@ -0,0 +1,8 @@ +module top(input [3:0] i, output [3:0] o); + python_inv #( + .width(4) + ) inv ( + .i(i), + .o(o), + ); +endmodule diff --git a/tests/rpc/exec.ys b/tests/rpc/exec.ys new file mode 100644 index 000000000..b46009fb9 --- /dev/null +++ b/tests/rpc/exec.ys @@ -0,0 +1,5 @@ +connect_rpc -exec python3 frontend.py stdio +read_verilog design.v +hierarchy -top top +flatten +select -assert-count 1 t:$neg diff --git a/tests/rpc/frontend.py b/tests/rpc/frontend.py new file mode 100644 index 000000000..eff41738a --- /dev/null +++ b/tests/rpc/frontend.py @@ -0,0 +1,126 @@ +def modules(): + return ["python_inv"] + +def derive(module, parameters): + assert module == r"python_inv" + if parameters.keys() != {r"\width"}: + raise ValueError("Invalid parameters") + return "ilang", r""" +module \impl + wire width {width:d} input 1 \i + wire width {width:d} output 2 \o + cell $neg $0 + parameter \A_SIGNED 1'0 + parameter \A_WIDTH 32'{width:b} + parameter \Y_WIDTH 32'{width:b} + connect \A \i + connect \Y \o + end +end +module \python_inv + wire width {width:d} input 1 \i + wire width {width:d} output 2 \o + cell \impl $0 + connect \i \i + connect \o \o + end +end +""".format(width=parameters[r"\width"]) + +# ---------------------------------------------------------------------------- + +import json +import argparse +import sys, socket, os +try: + import msvcrt, win32pipe, win32file +except ImportError: + msvcrt = win32pipe = win32file = None + +def map_parameter(parameter): + if parameter["type"] == "unsigned": + return int(parameter["value"], 2) + if parameter["type"] == "signed": + width = len(parameter["value"]) + value = int(parameter["value"], 2) + if value & (1 << (width - 1)): + value = -((1 << width) - value) + return value + if parameter["type"] == "string": + return parameter["value"] + if parameter["type"] == "real": + return float(parameter["value"]) + +def call(input_json): + input = json.loads(input_json) + if input["method"] == "modules": + return json.dumps({"modules": modules()}) + if input["method"] == "derive": + try: + frontend, source = derive(input["module"], + {name: map_parameter(value) for name, value in input["parameters"].items()}) + return json.dumps({"frontend": frontend, "source": source}) + except ValueError as e: + return json.dumps({"error": str(e)}) + +def main(): + parser = argparse.ArgumentParser() + modes = parser.add_subparsers(dest="mode") + mode_stdio = modes.add_parser("stdio") + if os.name == "posix": + mode_path = modes.add_parser("unix-socket") + if os.name == "nt": + mode_path = modes.add_parser("named-pipe") + mode_path.add_argument("path") + args = parser.parse_args() + + if args.mode == "stdio": + while True: + input = sys.stdin.readline() + if not input: break + sys.stdout.write(call(input) + "\n") + sys.stdout.flush() + + if args.mode == "unix-socket": + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind(args.path) + try: + sock.listen(1) + conn, addr = sock.accept() + file = conn.makefile("rw") + while True: + input = file.readline() + if not input: break + file.write(call(input) + "\n") + file.flush() + finally: + sock.close() + os.unlink(args.path) + + if args.mode == "named-pipe": + pipe = win32pipe.CreateNamedPipe(args.path, win32pipe.PIPE_ACCESS_DUPLEX, + win32pipe.PIPE_TYPE_BYTE|win32pipe.PIPE_READMODE_BYTE|win32pipe.PIPE_WAIT, + 1, 4096, 4096, 0, None) + win32pipe.ConnectNamedPipe(pipe, None) + try: + while True: + input = b"" + while not input.endswith(b"\n"): + result, data = win32file.ReadFile(pipe, 4096) + assert result == 0 + input += data + assert not b"\n" in input or input.endswith(b"\n") + output = (call(input.decode("utf-8")) + "\n").encode("utf-8") + length = len(output) + while length > 0: + result, done = win32file.WriteFile(pipe, output) + assert result == 0 + length -= done + except win32file.error as e: + if e.args[0] == 109: # ERROR_BROKEN_PIPE + pass + else: + raise + +if __name__ == "__main__": + main() diff --git a/tests/rpc/run-test.sh b/tests/rpc/run-test.sh new file mode 100755 index 000000000..44ce7e674 --- /dev/null +++ b/tests/rpc/run-test.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e +for x in *.ys; do + echo "Running $x.." + ../../yosys -ql ${x%.ys}.log $x +done diff --git a/tests/rpc/unix.ys b/tests/rpc/unix.ys new file mode 100644 index 000000000..cc7ec14ab --- /dev/null +++ b/tests/rpc/unix.ys @@ -0,0 +1,6 @@ +!python3 frontend.py unix-socket frontend.sock & sleep 0.1 +connect_rpc -path frontend.sock +read_verilog design.v +hierarchy -top top +flatten +select -assert-count 1 t:$neg