From 5524d5185d0d10246fb5410025c39e458e1a9abb Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 28 Nov 2022 19:06:59 +0100 Subject: [PATCH] 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; }