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.
This commit is contained in:
whitequark 2019-09-26 03:57:16 +00:00
parent 8f2bdff7b9
commit 99a7f39084
9 changed files with 744 additions and 0 deletions

View File

@ -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 ""

View File

@ -0,0 +1,2 @@
OBJS += frontends/rpc/rpc_frontend.o

View File

@ -0,0 +1,589 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2019 whitequark <whitequark@whitequark.org>
*
* 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 <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/un.h>
#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<std::string> get_module_names() {
Json response = call(Json::object {
{ "method", "modules" },
});
bool is_valid = true;
std::vector<std::string> 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<std::string, std::string> derive_module(const std::string &module, const dict<RTLIL::IdString, RTLIL::Const> &parameters) {
Json::object json_parameters;
for (auto &param : 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<RpcServer> server;
RTLIL::IdString derive(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Const> 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 &param : 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, "<rpc>" + derived_name.substr(8), command);
derived_design->check();
dict<std::string, std::string> 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 <command> [args...]\n");
log(" connect_rpc -path <path>\n");
log("\n");
log("Load modules using an out-of-process frontend.\n");
log("\n");
log(" -exec <command> [args...]\n");
log(" run <command> with arguments [args...]. send requests on stdin, read\n");
log(" responses from stdout.\n");
log("\n");
log(" -path <path>\n");
log(" connect to Unix domain socket at <path>. (Unix)\n");
log(" connect to bidirectional byte-type named pipe at <path>. (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\": [\"<module-name>\", ...]}\n");
log(" <- {\"error\": \"<error-message>\"}\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 <module-name> is instantiated in the design.\n");
log("\n");
log(" -> {\"method\": \"derive\", \"module\": \"<module-name\">, \"parameters\": {\n");
log(" \"<param-name>\": {\"type\": \"[unsigned|signed|string|real]\",\n");
log(" \"value\": \"<param-value>\"}, ...}}\n");
log(" <- {\"frontend\": \"[ilang|verilog|...]\",\"source\": \"<source>\"}}\n");
log(" <- {\"error\": \"<error-message>\"}\n");
log(" request for the module <module-name> to be derived for a specific set of\n");
log(" parameters. <param-name> starts with \\ for named parameters, and with $\n");
log(" for unnamed parameters, which are numbered starting at 1.<param-value>\n");
log(" for integer parameters is always specified as a binary string of unlimited\n");
log(" precision. the <source> returned by the frontend is hygienically parsed\n");
log(" by a built-in Yosys <frontend>, 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<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{
log_header(design, "Connecting to RPC frontend.\n");
std::vector<std::string> 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<RpcServer> 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<HandleRpcServer>(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<char *> 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<FdRpcServer>(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<HandleRpcServer>(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<FdRpcServer>(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

1
tests/rpc/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.log

8
tests/rpc/design.v Normal file
View File

@ -0,0 +1,8 @@
module top(input [3:0] i, output [3:0] o);
python_inv #(
.width(4)
) inv (
.i(i),
.o(o),
);
endmodule

5
tests/rpc/exec.ys Normal file
View File

@ -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

126
tests/rpc/frontend.py Normal file
View File

@ -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()

6
tests/rpc/run-test.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
set -e
for x in *.ys; do
echo "Running $x.."
../../yosys -ql ${x%.ys}.log $x
done

6
tests/rpc/unix.ys Normal file
View File

@ -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