From 9cbb17061dbb3a2c59508648e6c265c52eae506e Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:10:10 +1300 Subject: [PATCH] WIP docs: Proto log_help Define `PrettyHelp` class with methods for declaring different parts of help message. Currently able to produce standard help messages as expected. Updates chformal to use (only) the new help_v2. Currently makes use of a global static to track the current help context, allowing register.h to live in blissful ignorance and instead rely on help_v2 implementations calling `auto *help = PrettyHelp::get_current();` and `return true;` to minimise impact on rebuilds (i.e. not requiring every source file to be recompiled). --- Makefile | 1 + kernel/log_help.cc | 116 +++++++++++++++++++++ kernel/log_help.h | 51 +++++++++ kernel/register.cc | 118 ++++----------------- kernel/register.h | 27 +---- passes/cmds/chformal.cc | 225 +++++++++------------------------------- 6 files changed, 240 insertions(+), 298 deletions(-) create mode 100644 kernel/log_help.cc create mode 100644 kernel/log_help.h diff --git a/Makefile b/Makefile index 07460c5c5..55126969e 100644 --- a/Makefile +++ b/Makefile @@ -610,6 +610,7 @@ $(eval $(call add_include_file,frontends/blif/blifparse.h)) $(eval $(call add_include_file,backends/rtlil/rtlil_backend.h)) OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o +OBJS += kernel/log_help.o OBJS += kernel/binding.o kernel/tclapi.o OBJS += kernel/cellaigs.o kernel/celledges.o kernel/cost.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o kernel/sexpr.o OBJS += kernel/drivertools.o kernel/functional.o diff --git a/kernel/log_help.cc b/kernel/log_help.cc new file mode 100644 index 000000000..fe9829af1 --- /dev/null +++ b/kernel/log_help.cc @@ -0,0 +1,116 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2025 Krystine Dawn + * + * 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. + * + */ + +#include "kernel/log_help.h" + +USING_YOSYS_NAMESPACE + +#define MAX_LINE_LEN 80 +void log_pass_str(const std::string &pass_str, std::string indent_str, bool leading_newline=false) { + if (pass_str.empty()) + return; + std::istringstream iss(pass_str); + if (leading_newline) + log("\n"); + for (std::string line; std::getline(iss, line);) { + log("%s", indent_str.c_str()); + auto curr_len = indent_str.length(); + std::istringstream lss(line); + for (std::string word; std::getline(lss, word, ' ');) { + while (word[0] == '`' && word.back() == '`') + word = word.substr(1, word.length()-2); + if (curr_len + word.length() >= MAX_LINE_LEN-1) { + curr_len = 0; + log("\n%s", indent_str.c_str()); + } + if (word.length()) { + log("%s ", word.c_str()); + curr_len += word.length() + 1; + } + } + log("\n"); + } +} +void log_pass_str(const std::string &pass_str, int indent=0, bool leading_newline=false) { + std::string indent_str(indent*4, ' '); + log_pass_str(pass_str, indent_str, leading_newline); +} + +PrettyHelp *current_help = nullptr; + +PrettyHelp::PrettyHelp() +{ + prior = current_help; + current_help = this; +} + +PrettyHelp::~PrettyHelp() +{ + current_help = prior; +} + +PrettyHelp *PrettyHelp::get_current() +{ + if (current_help != nullptr) + return current_help; + else + return new PrettyHelp(); +} + +bool PrettyHelp::has_content() +{ + return false; +} + +void PrettyHelp::usage(const string &usage) +{ + log_pass_str(usage, current_indent+1, true); + log("\n"); +} + +void PrettyHelp::option(const string &option, const string &description) +{ + log_pass_str(option, current_indent); + if (description.length()) { + log_pass_str(description, current_indent+1); + log("\n"); + } +} + +void PrettyHelp::codeblock(const string &code, const string &) +{ + log("%s\n", code.c_str()); +} + +void PrettyHelp::paragraph(const string &text) +{ + log_pass_str(text, current_indent); + log("\n"); +} + +void PrettyHelp::optiongroup(const string &) +{ + current_indent += 1; +} + +void PrettyHelp::endgroup() +{ + current_indent -= 1; + log_assert(current_indent >= 0); +} diff --git a/kernel/log_help.h b/kernel/log_help.h new file mode 100644 index 000000000..bc62b20a6 --- /dev/null +++ b/kernel/log_help.h @@ -0,0 +1,51 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2025 Krystine Dawn + * + * 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. + * + */ + +#ifndef LOG_HELP_H +#define LOG_HELP_H + +#include "kernel/yosys_common.h" +#include "kernel/json.h" + +YOSYS_NAMESPACE_BEGIN + +class PrettyHelp +{ + PrettyHelp *prior = nullptr; + int current_indent = 0; +public: + PrettyHelp(); + ~PrettyHelp(); + + static PrettyHelp *get_current(); + + bool has_content(); + + void usage(const string &usage); + void option(const string &option, const string &description = ""); + void codeblock(const string &code, const string &language = "none"); + void paragraph(const string &text); + + void optiongroup(const string &group = ""); + void endgroup(); +}; + +YOSYS_NAMESPACE_END + +#endif diff --git a/kernel/register.cc b/kernel/register.cc index 70d7b5ccb..003a69106 100644 --- a/kernel/register.cc +++ b/kernel/register.cc @@ -20,6 +20,7 @@ #include "kernel/yosys.h" #include "kernel/satgen.h" #include "kernel/json.h" +#include "kernel/log_help.h" #include #include @@ -99,7 +100,7 @@ std::map backend_register; std::vector Frontend::next_args; -Pass::Pass(std::string name, std::string short_help, const vector doc_string, const vector usages) : pass_name(name), short_help(short_help), doc_string(doc_string), pass_usages(usages) +Pass::Pass(std::string name, std::string short_help) : pass_name(name), short_help(short_help) { next_queued_pass = first_queued_pass; first_queued_pass = this; @@ -172,79 +173,20 @@ void Pass::post_execute(Pass::pre_post_exec_state_t state) current_pass->runtime_ns -= time_ns; } -#define MAX_LINE_LEN 80 -void log_pass_str(const std::string &pass_str, std::string indent_str, bool leading_newline=false) { - if (pass_str.empty()) - return; - std::istringstream iss(pass_str); - if (leading_newline) - log("\n"); - for (std::string line; std::getline(iss, line);) { - log("%s", indent_str.c_str()); - auto curr_len = indent_str.length(); - std::istringstream lss(line); - for (std::string word; std::getline(lss, word, ' ');) { - while (word[0] == '`' && word.back() == '`') - word = word.substr(1, word.length()-2); - if (curr_len + word.length() >= MAX_LINE_LEN-1) { - curr_len = 0; - log("\n%s", indent_str.c_str()); - } - if (word.length()) { - log("%s ", word.c_str()); - curr_len += word.length() + 1; - } - } - log("\n"); - } -} -void log_pass_str(const std::string &pass_str, int indent=0, bool leading_newline=false) { - std::string indent_str(indent*4, ' '); - log_pass_str(pass_str, indent_str, leading_newline); -} - void Pass::help() { - if (HasUsages()) { - for (auto usage : pass_usages) { - log_pass_str(usage.signature, 1, true); - log_pass_str(usage.description, 0, true); - for (auto option : usage.options) { - log_pass_str(option.keyword, 1, true); - log_pass_str(option.description, 2, false); - } - log_pass_str(usage.postscript, 0, true); - } - log("\n"); - } else if (HasDocstring()) { - log("\n"); - auto print_empty = true; - for (auto doc_line : doc_string) { - if (doc_line.find("..") == 0 && doc_line.find(":: ") != std::string::npos) { - auto command_pos = doc_line.find(":: "); - auto command_str = doc_line.substr(0, command_pos); - if (command_str.compare(".. cmd:usage") == 0) { - log_pass_str(doc_line.substr(command_pos+3), 1); - } else { - print_empty = false; - } - } else if (doc_line.length()) { - std::size_t first_pos = doc_line.find_first_not_of(" \t"); - auto indent_str = doc_line.substr(0, first_pos); - log_pass_str(doc_line, indent_str); - print_empty = true; - } else if (print_empty) { - log("\n"); - } - } - log("\n"); - } else { + if (!help_v2()) { log("\n"); log("No help message for command `%s'.\n", pass_name.c_str()); log("\n"); } } +bool Pass::help_v2() +{ + return false; +} + void Pass::clear_flags() { } @@ -975,13 +917,12 @@ struct HelpPass : public Pass { if (name.compare("verific") != 0 && name.find("chformal") == string::npos) continue; auto pass = it.second; auto title = pass->short_help; - vector usages; auto experimental_flag = pass->experimental_flag; - if (pass->HasUsages() || pass->HasDocstring()) { - for (auto usage : pass->pass_usages) - usages.push_back(usage); - } else { + auto cmd_help = PrettyHelp(); + auto has_pretty_help = pass->help_v2(); + + if (!has_pretty_help) { enum PassUsageState { PUState_signature, PUState_description, @@ -1029,7 +970,7 @@ struct HelpPass : public Pass { if (NewUsage) { current_state = PUState_signature; - usages.push_back({}); + // usages.push_back({}); def_strip_count = first_pos; catch_verific = false; } else if (IsDedent) { @@ -1042,19 +983,19 @@ struct HelpPass : public Pass { if (IsDefinition && IsIndent && !catch_verific) { current_state = PUState_options; - usages.back().options.push_back(PassOption({stripped_line, ""})); + // usages.back().options.push_back(PassOption({stripped_line, ""})); def_strip_count = first_pos; } else { string *desc_str; if (current_state == PUState_signature) { - desc_str = &(usages.back().signature); + // desc_str = &(usages.back().signature); blank_count += 1; } else if (current_state == PUState_description) { - desc_str = &(usages.back().description); + // desc_str = &(usages.back().description); } else if (current_state == PUState_options) { - desc_str = &(usages.back().options.back().description); + // desc_str = &(usages.back().options.back().description); } else if (current_state == PUState_postscript) { - desc_str = &(usages.back().postscript); + // desc_str = &(usages.back().postscript); } else { log_abort(); } @@ -1075,28 +1016,7 @@ struct HelpPass : public Pass { // write to json json.name(name.c_str()); json.begin_object(); json.entry("title", title); - if (pass->HasDocstring()) { - json.entry("content", pass->doc_string); - } - if (usages.size()) { - json.name("usages"); json.begin_array(); - for (auto usage : usages) { - json.begin_object(); - json.entry("signature", usage.signature); - json.entry("description", usage.description); - json.name("options"); json.begin_array(); - for (auto option : usage.options) { - json.begin_array(); - json.value(option.keyword); - json.value(option.description); - json.end_array(); - } - json.end_array(); - json.entry("postscript", usage.postscript); - json.end_object(); - } - json.end_array(); - } + // json.entry("content", cmd_help); json.entry("experimental_flag", experimental_flag); json.end_object(); } diff --git a/kernel/register.h b/kernel/register.h index 64b2ce42f..6e7b74d5c 100644 --- a/kernel/register.h +++ b/kernel/register.h @@ -25,29 +25,14 @@ YOSYS_NAMESPACE_BEGIN -struct PassOption { - string keyword; - string description; -}; - -struct PassUsageBlock { - string signature = ""; - string description = ""; - vector options = {}; - string postscript = ""; -}; - struct Pass { std::string pass_name, short_help; - const vector doc_string; - const vector pass_usages; - Pass(std::string name, std::string short_help = "** document me **", - const vector doc_string = {}, - const vector usages = {}); + Pass(std::string name, std::string short_help = "** document me **"); virtual ~Pass(); virtual void help(); + virtual bool help_v2(); virtual void clear_flags(); virtual void execute(std::vector args, RTLIL::Design *design) = 0; @@ -59,14 +44,6 @@ struct Pass experimental_flag = true; } - bool HasUsages() { - return !pass_usages.empty(); - } - - bool HasDocstring() { - return !doc_string.empty(); - } - struct pre_post_exec_state_t { Pass *parent_pass; int64_t begin_ns; diff --git a/passes/cmds/chformal.cc b/passes/cmds/chformal.cc index 9d1681e4f..e2f1e0406 100644 --- a/passes/cmds/chformal.cc +++ b/passes/cmds/chformal.cc @@ -19,6 +19,7 @@ #include "kernel/yosys.h" #include "kernel/sigtools.h" +#include "kernel/log_help.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -69,183 +70,59 @@ static bool is_triggered_check_cell(RTLIL::Cell * cell) return cell->type == ID($check) && cell->getParam(ID(TRG_ENABLE)).as_bool(); } -struct ChformalPassStruct : public Pass { - ChformalPassStruct() : Pass("chformal_struct", "change formal constraints of the design", {}, { - { - .signature = "chformal [types] [mode] [options] [selection]", - .description = "Make changes to the formal constraints of the design. The [types] options the type of " - "constraint to operate on. If none of the following options are given, the command " - "will operate on all constraint types:", - .options = { - {"-assert $assert cells, representing assert(...) constraints" - "\n-assume $assume cells, representing assume(...) constraints" - "\n-live $live cells, representing assert(s_eventually ...)" - "\n-fair $fair cells, representing assume(s_eventually ...)" - "\n-cover $cover cells, representing cover() statements", ""}, - {"Additionally chformal will operate on $check cells corresponding to the selected constraint " - "types.", ""}, - } - }, - { - .description = "Exactly one of the following modes must be specified:", - .options = { - {"-remove", - "remove the cells and thus constraints from the design"}, - {"-early", - "bypass FFs that only delay the activation of a constraint. When inputs " - "of the bypassed FFs do not remain stable between clock edges, this may " - "result in unexpected behavior." - }, - {"-delay ", - "delay activation of the constraint by clock cycles" - }, - {"-skip ", - "ignore activation of the constraint in the first clock cycles" - }, - {"-coverenable", - "add cover statements for the enable signals of the constraints" -#ifdef YOSYS_ENABLE_VERIFIC - "\n\nNote: For the Verific frontend it is currently not guaranteed that a " - "reachable SVA statement corresponds to an active enable signal." -#endif - }, - {"-assert2assume" - "\n-assume2assert" - "\n-live2fair" - "\n-fair2live", - "change the roles of cells as indicated. these options can be combined" - }, - {"-lower", - "convert each $check cell into an $assert, $assume, $live, $fair or " - "$cover cell. If the $check cell contains a message, also produce a " - "$print cell." - }, - } - }, - }) { } - - void execute(std::vector args, RTLIL::Design *design) override {} - -} ChformalPassStruct; - -struct ChformalPassDocs : public Pass { - ChformalPassDocs() : Pass("chformal_docstring", "change formal constraints of the design", { - // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| - ".. cmd:usage:: chformal [types] [mode] [options] [selection]", - "", - "Make changes to the formal constraints of the design. The [types] options " - "the type of constraint to operate on. If none of the following options are " - "given, the command will operate on all constraint types:", - "", - ".. cmd:optiongroup:: [types]", - "", - " :option -assert: `$assert` cells, representing ``assert(...)`` constraints", - " :option -assume: `$assume` cells, representing ``assume(...)`` constraints", - " :option -live: `$live` cells, representing ``assert(s_eventually ...)``", - " :option -fair: `$fair` cells, representing ``assume(s_eventually ...)``", - " :option -cover: `$cover` cells, representing ``cover()`` statements", - "", - " Additionally chformal will operate on `$check` cells corresponding to the " - " selected constraint types.", - "", - "Exactly one of the following modes must be specified:", - "", - ".. cmd:optiongroup:: [mode]", - "", - " :option -remove:", - " remove the cells and thus constraints from the design", - "", - " :option -early:", - " bypass FFs that only delay the activation of a constraint. When inputs " - " of the bypassed FFs do not remain stable between clock edges, this may " - " result in unexpected behavior.", - "", - " :option -delay :", - " delay activation of the constraint by clock cycles", - "", - " :option -skip :", - " ignore activation of the constraint in the first clock cycles", - "", - " :option -coverenable:", - " add cover statements for the enable signals of the constraints", -#ifdef YOSYS_ENABLE_VERIFIC - "", - " Note: For the Verific frontend it is currently not guaranteed that a " - " reachable SVA statement corresponds to an active enable signal.", -#endif - "", - " :option -assert2assume:", - " :option -assume2assert:", - " :option -live2fair:", - " :option -fair2live:", - " change the roles of cells as indicated. these options can be combined", - "", - " :option -lower:", - " convert each $check cell into an $assert, $assume, $live, $fair or " - " $cover cell. If the $check cell contains a message, also produce a " - " $print cell.", - }) { } - - void execute(std::vector args, RTLIL::Design *design) override {} - -} ChformalPassDocs; - struct ChformalPass : public Pass { - ChformalPass() : Pass("chformal", "change formal constraints of the design") {}; - void help() override - { - // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| - log("\n"); - log(" chformal [types] [mode] [options] [selection]\n"); - log("\n"); - log("Make changes to the formal constraints of the design. The [types] options\n"); - log("the type of constraint to operate on. If none of the following options are\n"); - log("given, the command will operate on all constraint types:\n"); - log("\n"); - log(" -assert $assert cells, representing assert(...) constraints\n"); - log(" -assume $assume cells, representing assume(...) constraints\n"); - log(" -live $live cells, representing assert(s_eventually ...)\n"); - log(" -fair $fair cells, representing assume(s_eventually ...)\n"); - log(" -cover $cover cells, representing cover() statements\n"); - log("\n"); - log(" Additionally chformal will operate on $check cells corresponding to the\n"); - log(" selected constraint types.\n"); - log("\n"); - log("Exactly one of the following modes must be specified:\n"); - log("\n"); - log(" -remove\n"); - log(" remove the cells and thus constraints from the design\n"); - log("\n"); - log(" -early\n"); - log(" bypass FFs that only delay the activation of a constraint. When inputs\n"); - log(" of the bypassed FFs do not remain stable between clock edges, this may\n"); - log(" result in unexpected behavior.\n"); - log("\n"); - log(" -delay \n"); - log(" delay activation of the constraint by clock cycles\n"); - log("\n"); - log(" -skip \n"); - log(" ignore activation of the constraint in the first clock cycles\n"); - log("\n"); - log(" -coverenable\n"); - log(" add cover statements for the enable signals of the constraints\n"); - log("\n"); + ChformalPass() : Pass("chformal", "change formal constraints of the design") {} + + bool help_v2() override { + auto *help = PrettyHelp::get_current(); + help->usage("chformal [types] [mode] [options] [selection]"); + help->paragraph( + "Make changes to the formal constraints of the design. The [types] options " + "the type of constraint to operate on. If none of the following options are " + "given, the command will operate on all constraint types:" + ); + + help->optiongroup("[types]"); + help->option("-assert", "`$assert` cells, representing ``assert(...)`` constraints"); + help->option("-assume", "`$assume` cells, representing ``assume(...)`` constraints"); + help->option("-live", "`$live` cells, representing ``assert(s_eventually ...)``"); + help->option("-fair", "`$fair` cells, representing ``assume(s_eventually ...)``"); + help->option("-cover", "`$cover` cells, representing ``cover()`` statements"); + help->paragraph( + "Additionally chformal will operate on `$check` cells corresponding to the " + "selected constraint types." + ); + help->endgroup(); + + help->paragraph("Exactly one of the following modes must be specified:"); + + help->optiongroup("[mode]"); + help->option("-remove", "remove the cells and thus constraints from the design"); + help->option("-early", + "bypass FFs that only delay the activation of a constraint. When inputs " + "of the bypassed FFs do not remain stable between clock edges, this may " + "result in unexpected behavior." + ); + help->option("-delay ", "delay activation of the constraint by clock cycles"); + help->option("-skip ", "ignore activation of the constraint in the first clock cycles"); + help->option("-coverenable", + "add cover statements for the enable signals of the constraints" #ifdef YOSYS_ENABLE_VERIFIC - log(" Note: For the Verific frontend it is currently not guaranteed that a\n"); - log(" reachable SVA statement corresponds to an active enable signal.\n"); - log("\n"); + "\n\n" + "Note: For the Verific frontend it is currently not guaranteed that a " + "reachable SVA statement corresponds to an active enable signal." #endif - log(" -assert2assume\n"); - log(" -assume2assert\n"); - log(" -live2fair\n"); - log(" -fair2live\n"); - log(" change the roles of cells as indicated. these options can be combined\n"); - log("\n"); - log(" -lower\n"); - log(" convert each $check cell into an $assert, $assume, $live, $fair or\n"); - log(" $cover cell. If the $check cell contains a message, also produce a\n"); - log(" $print cell.\n"); - log("\n"); + ); + help->option("-assert2assume"); + help->option("-assume2assert"); + help->option("-live2fair"); + help->option("-fair2live", "change the roles of cells as indicated. these options can be combined"); + help->option("-lower", + "convert each $check cell into an $assert, $assume, $live, $fair or " + "$cover cell. If the $check cell contains a message, also produce a " + "$print cell." + ); + return true; } void execute(std::vector args, RTLIL::Design *design) override {