From ed02d52f30d3c2b3d6a4ba0e8234cc07c78889dc Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 28 Nov 2022 19:03:27 +0100 Subject: [PATCH 1/6] tee: Allow logging command output to a given scratchpad value --- kernel/log.cc | 6 ++++++ kernel/log.h | 1 + passes/cmds/tee.cc | 13 +++++++++++++ 3 files changed, 20 insertions(+) diff --git a/kernel/log.cc b/kernel/log.cc index af8c422b8..25d198744 100644 --- a/kernel/log.cc +++ b/kernel/log.cc @@ -40,6 +40,7 @@ YOSYS_NAMESPACE_BEGIN std::vector log_files; std::vector log_streams; +std::vector log_scratchpads; std::map> log_hdump; std::vector log_warn_regexes, log_nowarn_regexes, log_werror_regexes; dict log_expect_log, log_expect_warning, log_expect_error; @@ -158,6 +159,11 @@ void logv(const char *format, va_list ap) for (auto f : log_streams) *f << str; + RTLIL::Design *design = yosys_get_design(); + if (design != nullptr) + for (auto &scratchpad : log_scratchpads) + design->scratchpad[scratchpad].append(str); + static std::string linebuffer; static bool log_warn_regex_recusion_guard = false; diff --git a/kernel/log.h b/kernel/log.h index 822816cb4..35368a683 100644 --- a/kernel/log.h +++ b/kernel/log.h @@ -133,6 +133,7 @@ struct log_cmd_error_exception { }; extern std::vector log_files; extern std::vector log_streams; +extern std::vector log_scratchpads; extern std::map> log_hdump; extern std::vector log_warn_regexes, log_nowarn_regexes, log_werror_regexes; extern std::set log_warnings, log_experimentals, log_experimentals_ignored; diff --git a/passes/cmds/tee.cc b/passes/cmds/tee.cc index 7a1f4a36b..39ed4a7a8 100644 --- a/passes/cmds/tee.cc +++ b/passes/cmds/tee.cc @@ -45,6 +45,9 @@ struct TeePass : public Pass { log(" -a logfile\n"); log(" Write output to this file, append if exists.\n"); log("\n"); + log(" -s scratchpad\n"); + log(" Write output to this scratchpad value, truncate if it exists.\n"); + log("\n"); log(" +INT, -INT\n"); log(" Add/subtract INT from the -v setting for this command.\n"); log("\n"); @@ -53,9 +56,11 @@ struct TeePass : public Pass { { std::vector backup_log_files, files_to_close; std::vector backup_log_streams; + std::vector backup_log_scratchpads; int backup_log_verbose_level = log_verbose_level; backup_log_streams = log_streams; backup_log_files = log_files; + backup_log_scratchpads = log_scratchpads; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) @@ -78,6 +83,12 @@ struct TeePass : public Pass { files_to_close.push_back(f); continue; } + if (args[argidx] == "-s" && argidx+1 < args.size()) { + auto name = args[++argidx]; + design->scratchpad[name] = ""; + log_scratchpads.push_back(name); + continue; + } if (GetSize(args[argidx]) >= 2 && (args[argidx][0] == '-' || args[argidx][0] == '+') && args[argidx][1] >= '0' && args[argidx][1] <= '9') { log_verbose_level += atoi(args[argidx].c_str()); continue; @@ -93,6 +104,7 @@ struct TeePass : public Pass { fclose(cf); log_files = backup_log_files; log_streams = backup_log_streams; + log_scratchpads = backup_log_scratchpads; throw; } @@ -102,6 +114,7 @@ struct TeePass : public Pass { log_verbose_level = backup_log_verbose_level; log_files = backup_log_files; log_streams = backup_log_streams; + log_scratchpads = backup_log_scratchpads; } } TeePass; From 7036a312bf1fba8ea88c0b1227bf0f84362e5f9a Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 28 Nov 2022 19:05:12 +0100 Subject: [PATCH 2/6] stat: Fix JSON output for empty designs --- passes/cmds/stat.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc index a998ab8e7..10e98952e 100644 --- a/passes/cmds/stat.cc +++ b/passes/cmds/stat.cc @@ -452,7 +452,7 @@ struct StatPass : public Pass { if (json_mode) { log("\n"); - log(" },\n"); + log(top_mod == nullptr ? " }\n" : " },\n"); } if (top_mod != nullptr) @@ -466,7 +466,7 @@ struct StatPass : public Pass { statdata_t data = hierarchy_worker(mod_stat, top_mod->name, 0, /*quiet=*/json_mode); - if (json_mode) + if (json_mode) data.log_data_json("design", true); else if (GetSize(mod_stat) > 1) { log("\n"); From 5524d5185d0d10246fb5410025c39e458e1a9abb Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 28 Nov 2022 19:06:59 +0100 Subject: [PATCH 3/6] tcl: Return scratchpad result.json and result.string as tcl objects This makes it possible for yosys commands to return values when invoked as tcl commands. Right now no commands natively support this, but the tee command can be used with json output like this: ```tcl set stat [yosys tee -q -s result.json stat -json -top top] dict get $stat modules \\top num_cells_by_type \$pmux ``` Or with newline separated lists like this: ```tcl split [yosys tee -q -s result.string select -list top] "\n" ``` --- kernel/yosys.cc | 59 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/kernel/yosys.cc b/kernel/yosys.cc index 01085d29b..c75500997 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -73,6 +73,8 @@ #include #include +#include "libs/json11/json11.hpp" + YOSYS_NAMESPACE_BEGIN int autoidx = 1; @@ -709,6 +711,42 @@ void rewrite_filename(std::string &filename) } #ifdef YOSYS_ENABLE_TCL + +static Tcl_Obj *json_to_tcl(Tcl_Interp *interp, const json11::Json &json) +{ + if (json.is_null()) + return Tcl_NewStringObj("null", 4); + else if (json.is_string()) { + auto string = json.string_value(); + return Tcl_NewStringObj(string.data(), string.size()); + } else if (json.is_number()) { + double value = json.number_value(); + double round_val = std::nearbyint(value); + if (std::isfinite(round_val) && value == round_val && value >= LONG_MIN && value < -double(LONG_MIN)) + return Tcl_NewLongObj((long)round_val); + else + return Tcl_NewDoubleObj(value); + } else if (json.is_bool()) { + return Tcl_NewBooleanObj(json.bool_value()); + } else if (json.is_array()) { + auto list = json.array_items(); + Tcl_Obj *result = Tcl_NewListObj(list.size(), nullptr); + for (auto &item : list) + Tcl_ListObjAppendElement(interp, result, json_to_tcl(interp, item)); + return result; + } else if (json.is_object()) { + auto map = json.object_items(); + Tcl_Obj *result = Tcl_NewListObj(map.size() * 2, nullptr); + for (auto &item : map) { + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(item.first.data(), item.first.size())); + Tcl_ListObjAppendElement(interp, result, json_to_tcl(interp, item.second)); + } + return result; + } else { + log_abort(); + } +} + static int tcl_yosys_cmd(ClientData, Tcl_Interp *interp, int argc, const char *argv[]) { std::vector args; @@ -733,12 +771,29 @@ static int tcl_yosys_cmd(ClientData, Tcl_Interp *interp, int argc, const char *a return TCL_OK; } + yosys_get_design()->scratchpad_unset("result.json"); + if (args.size() == 1) { Pass::call(yosys_get_design(), args[0]); - return TCL_OK; + } else { + Pass::call(yosys_get_design(), args); + } + + auto &scratchpad = yosys_get_design()->scratchpad; + auto result = scratchpad.find("result.json"); + if (result != scratchpad.end()) { + std::string err; + auto json = json11::Json::parse(result->second, err); + if (err.empty()) { + Tcl_SetObjResult(interp, json_to_tcl(interp, json)); + scratchpad.erase(result); + } else + log_warning("Ignoring result.json scratchpad value due to parse error: %s\n", err.c_str()); + } else if ((result = scratchpad.find("result.string")) != scratchpad.end()) { + Tcl_SetObjResult(interp, Tcl_NewStringObj(result->second.data(), result->second.size())); + scratchpad.erase(result); } - Pass::call(yosys_get_design(), args); return TCL_OK; } From 0f7b8b8d23b4f5ba6a4d0f37c821a8935cd0e508 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Fri, 2 Dec 2022 15:29:10 +0100 Subject: [PATCH 4/6] tcl: Don't exit repl on recoverable command errors --- kernel/driver.cc | 2 ++ kernel/yosys.cc | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/kernel/driver.cc b/kernel/driver.cc index aa90802c9..a89c790d9 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -205,6 +205,7 @@ extern char yosys_path[PATH_MAX]; #ifdef YOSYS_ENABLE_TCL namespace Yosys { extern int yosys_tcl_iterp_init(Tcl_Interp *interp); + extern void yosys_tcl_activate_repl(); }; #endif @@ -584,6 +585,7 @@ int main(int argc, char **argv) if (run_tcl_shell) { #ifdef YOSYS_ENABLE_TCL + yosys_tcl_activate_repl(); Tcl_Main(argc, argv, yosys_tcl_iterp_init); #else log_error("Can't exectue TCL shell: this version of yosys is not built with TCL support enabled.\n"); diff --git a/kernel/yosys.cc b/kernel/yosys.cc index c75500997..9ece3a2c0 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -84,6 +84,7 @@ CellTypes yosys_celltypes; #ifdef YOSYS_ENABLE_TCL Tcl_Interp *yosys_tcl_interp = NULL; +bool yosys_tcl_repl_active = false; #endif std::set yosys_input_files, yosys_output_files; @@ -773,12 +774,36 @@ static int tcl_yosys_cmd(ClientData, Tcl_Interp *interp, int argc, const char *a yosys_get_design()->scratchpad_unset("result.json"); - if (args.size() == 1) { - Pass::call(yosys_get_design(), args[0]); - } else { - Pass::call(yosys_get_design(), args); + bool in_repl = yosys_tcl_repl_active; + bool restore_log_cmd_error_throw = log_cmd_error_throw; + + log_cmd_error_throw = true; + + try { + if (args.size() == 1) { + Pass::call(yosys_get_design(), args[0]); + } else { + Pass::call(yosys_get_design(), args); + } + } catch (log_cmd_error_exception) { + if (in_repl) { + auto design = yosys_get_design(); + while (design->selection_stack.size() > 1) + design->selection_stack.pop_back(); + log_reset_stack(); + } + Tcl_SetResult(interp, (char *)"Yosys command produced an error", TCL_STATIC); + + yosys_tcl_repl_active = in_repl; + log_cmd_error_throw = restore_log_cmd_error_throw; + return TCL_ERROR; + } catch (...) { + log_error("uncaught exception during Yosys command invoked from TCL\n"); } + yosys_tcl_repl_active = in_repl; + log_cmd_error_throw = restore_log_cmd_error_throw; + auto &scratchpad = yosys_get_design()->scratchpad; auto result = scratchpad.find("result.json"); if (result != scratchpad.end()) { @@ -805,6 +830,11 @@ int yosys_tcl_iterp_init(Tcl_Interp *interp) return TCL_OK ; } +void yosys_tcl_activate_repl() +{ + yosys_tcl_repl_active = true; +} + extern Tcl_Interp *yosys_get_tcl_interp() { if (yosys_tcl_interp == NULL) { From a43356cb049fed7360acd60ac5aa01602d5809e1 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 5 Dec 2022 16:17:00 +0100 Subject: [PATCH 5/6] tcl: Unset both result.json and result.string only before calling pass --- kernel/yosys.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/yosys.cc b/kernel/yosys.cc index 9ece3a2c0..69db9df66 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -773,6 +773,7 @@ static int tcl_yosys_cmd(ClientData, Tcl_Interp *interp, int argc, const char *a } yosys_get_design()->scratchpad_unset("result.json"); + yosys_get_design()->scratchpad_unset("result.string"); bool in_repl = yosys_tcl_repl_active; bool restore_log_cmd_error_throw = log_cmd_error_throw; @@ -811,12 +812,10 @@ static int tcl_yosys_cmd(ClientData, Tcl_Interp *interp, int argc, const char *a auto json = json11::Json::parse(result->second, err); if (err.empty()) { Tcl_SetObjResult(interp, json_to_tcl(interp, json)); - scratchpad.erase(result); } else log_warning("Ignoring result.json scratchpad value due to parse error: %s\n", err.c_str()); } else if ((result = scratchpad.find("result.string")) != scratchpad.end()) { Tcl_SetObjResult(interp, Tcl_NewStringObj(result->second.data(), result->second.size())); - scratchpad.erase(result); } return TCL_OK; From 6589accfa9e858cc93b87d09cf6644aeea5151a4 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 5 Dec 2022 16:47:22 +0100 Subject: [PATCH 6/6] tcl: Update help message to mention 'tee -s' --- kernel/yosys.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/yosys.cc b/kernel/yosys.cc index 69db9df66..333faae6a 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -862,8 +862,8 @@ struct TclPass : public Pass { log("the standard $argc and $argv variables.\n"); log("\n"); log("Note, tcl will not recieve the output of any yosys command. If the output\n"); - log("of the tcl commands are needed, use the yosys command 'tee' to redirect yosys's\n"); - log("output to a temporary file.\n"); + log("of the tcl commands are needed, use the yosys command 'tee -s result.string'\n"); + log("to redirect yosys's output to the 'result.string' scratchpad value.\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *) override {