From 4371cd50b641933ff9afefa2c175dd0a03af9f42 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 26 Sep 2024 11:12:47 +0200 Subject: [PATCH] driver: switch to cxxopts, replace -B --- .gitmodules | 3 + kernel/driver.cc | 480 +++++++++++++++++------------------------------ libs/cxxopts | 1 + 3 files changed, 172 insertions(+), 312 deletions(-) create mode 160000 libs/cxxopts diff --git a/.gitmodules b/.gitmodules index d88d4b1e5e9..de3bb2e7491 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "abc"] path = abc url = https://github.com/YosysHQ/abc +[submodule "libs/cxxopts"] + path = libs/cxxopts + url = git@github.com:jarro2783/cxxopts.git diff --git a/kernel/driver.cc b/kernel/driver.cc index 1e4cd005266..fe1091425c6 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -19,6 +19,8 @@ #include "kernel/yosys.h" #include "libs/sha1/sha1.h" +#include "libs/cxxopts/include/cxxopts.hpp" +#include #ifdef YOSYS_ENABLE_READLINE # include @@ -55,55 +57,6 @@ USING_YOSYS_NAMESPACE -char *optarg; -int optind = 1, optcur = 1, optopt = 0; -int getopt(int argc, char **argv, const char *optstring) -{ - if (optind >= argc) - return -1; - - if (argv[optind][0] != '-' || argv[optind][1] == 0) { - optopt = 1; - optarg = argv[optind++]; - return optopt; - } - - bool takes_arg = false; - optopt = argv[optind][optcur]; - - if (optopt == '-') { - ++optind; - return -1; - } - - for (int i = 0; optstring[i]; i++) - if (optopt == optstring[i] && optstring[i + 1] == ':') - takes_arg = true; - - if (!takes_arg) { - if (argv[optind][++optcur] == 0) - optind++, optcur = 1; - return optopt; - } - - if (argv[optind][++optcur]) { - optarg = argv[optind++] + optcur; - optcur = 1; - return optopt; - } - - if (++optind >= argc) { - fprintf(stderr, "%s: option '-%c' expects an argument\n", argv[0], optopt); - optopt = '?'; - return optopt; - } - - optarg = argv[optind]; - optind++, optcur = 1; - - return optopt; -} - #ifdef EMSCRIPTEN # include # include @@ -235,6 +188,7 @@ int main(int argc, char **argv) std::vector passes_commands; std::vector frontend_files; std::vector plugin_filenames; + std::vector special_args; std::string output_filename = ""; std::string scriptfile = ""; std::string depsfile = ""; @@ -250,292 +204,194 @@ int main(int argc, char **argv) bool mode_v = false; bool mode_q = false; - if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "-help") || !strcmp(argv[1], "--help"))) - { - printf("\n"); - printf("Usage: %s [options] [ [..]]\n", argv[0]); - printf("\n"); - printf(" -Q\n"); - printf(" suppress printing of banner (copyright, disclaimer, version)\n"); - printf("\n"); - printf(" -T\n"); - printf(" suppress printing of footer (log hash, version, timing statistics)\n"); - printf("\n"); - printf(" -q\n"); - printf(" quiet operation. only write warnings and error messages to console\n"); - printf(" use this option twice to also quiet warning messages\n"); - printf("\n"); - printf(" -v \n"); - printf(" print log headers up to level to the console. (this\n"); - printf(" implies -q for everything except the 'End of script.' message.)\n"); - printf("\n"); - printf(" -t\n"); - printf(" annotate all log messages with a time stamp\n"); - printf("\n"); - printf(" -d\n"); - printf(" print more detailed timing stats at exit\n"); - printf("\n"); - printf(" -l logfile\n"); - printf(" write log messages to the specified file\n"); - printf("\n"); - printf(" -L logfile\n"); - printf(" like -l but open log file in line buffered mode\n"); - printf("\n"); - printf(" -o outfile\n"); - printf(" write the design to the specified file on exit\n"); - printf("\n"); - printf(" -b backend\n"); - printf(" use this backend for the output file specified on the command line\n"); - printf("\n"); - printf(" -f frontend\n"); - printf(" use the specified frontend for the input files on the command line\n"); - printf("\n"); - printf(" -H\n"); - printf(" print the command list\n"); - printf("\n"); - printf(" -h command\n"); - printf(" print the help message for the specified command\n"); - printf("\n"); - printf(" -s scriptfile\n"); - printf(" execute the commands in the script file\n"); -#ifdef YOSYS_ENABLE_TCL - printf("\n"); - printf(" -c tcl_scriptfile\n"); - printf(" execute the commands in the tcl script file (see 'help tcl' for details)\n"); - printf("\n"); - printf(" -C\n"); - printf(" enters TCL interatcive shell mode\n"); -#endif - printf("\n"); - printf(" -p command\n"); - printf(" execute the commands (to chain commands, separate them with semicolon + whitespace: 'cmd1; cmd2')\n"); - printf("\n"); - printf(" -m module_file\n"); - printf(" load the specified module (aka plugin)\n"); - printf("\n"); - printf(" -X\n"); - printf(" enable tracing of core data structure changes. for debugging\n"); - printf("\n"); - printf(" -M\n"); - printf(" will slightly randomize allocated pointer addresses. for debugging\n"); - printf("\n"); - printf(" -A\n"); - printf(" will call abort() at the end of the script. for debugging\n"); - printf("\n"); - printf(" -r \n"); - printf(" elaborate command line arguments using the specified top module\n"); - printf("\n"); - printf(" -D [=]\n"); - printf(" set the specified Verilog define (via \"read -define\")\n"); - printf("\n"); - printf(" -P [:]\n"); - printf(" dump the design when printing the specified log header to a file.\n"); - printf(" yosys_dump_.il is used as filename if none is specified.\n"); - printf(" Use 'ALL' as to dump at every header.\n"); - printf("\n"); - printf(" -W regex\n"); - printf(" print a warning for all log messages matching the regex.\n"); - printf("\n"); - printf(" -w regex\n"); - printf(" if a warning message matches the regex, it is printed as regular\n"); - printf(" message instead.\n"); - printf("\n"); - printf(" -e regex\n"); - printf(" if a warning message matches the regex, it is printed as error\n"); - printf(" message instead and the tool terminates with a nonzero return code.\n"); - printf("\n"); - printf(" -E \n"); - printf(" write a Makefile dependencies file with in- and output file names\n"); - printf("\n"); - printf(" -x \n"); - printf(" do not print warnings for the specified experimental feature\n"); - printf("\n"); - printf(" -g\n"); - printf(" globally enable debug log messages\n"); - printf("\n"); - printf(" -V\n"); - printf(" print version information and exit\n"); - printf("\n"); - printf("The option -S is a shortcut for calling the \"synth\" command, a default\n"); - printf("script for transforming the Verilog input to a gate-level netlist. For example:\n"); - printf("\n"); - printf(" yosys -o output.blif -S input.v\n"); - printf("\n"); - printf("For more complex synthesis jobs it is recommended to use the read_* and write_*\n"); - printf("commands in a script file instead of specifying input and output files on the\n"); - printf("command line.\n"); - printf("\n"); - printf("When no commands, script files or input files are specified on the command\n"); - printf("line, yosys automatically enters the interactive command mode. Use the 'help'\n"); - printf("command to get information on the individual commands.\n"); - printf("\n"); + cxxopts::Options options("yosys", "Yosys Open SYnthesis Suite"); + + options.add_options() + ("Q", "suppress printing of banner (copyright, disclaimer, version)") + ("T", "suppress printing of footer (log hash, version, timing statistics)") + ("q,quiet", "quiet operation. only write warnings and error messages to console") + ("v,verbose", "print log headers up to level to the console", cxxopts::value()) + ("t,timestamp", "annotate all log messages with a time stamp") + ("d,detailed-timing", "print more detailed timing stats at exit") + ("l,logfile", "write log messages to the specified file", cxxopts::value>()) + ("L,line-buffered-logfile", "like -l but open log file in line buffered mode", cxxopts::value>()) + ("o,outfile", "write the design to the specified file on exit", cxxopts::value()) + ("b,backend", "use this backend for the output file specified on the command line", cxxopts::value()) + ("f,frontend", "use the specified frontend for the input files on the command line", cxxopts::value()) + ("H", "print the command list") + ("h,help", "print the help message for the specified command", cxxopts::value()) + ("s,scriptfile", "execute the commands in the script file", cxxopts::value()) + ("c,tcl-scriptfile", "execute the commands in the tcl script file", cxxopts::value()) + ("C,tcl-interactive", "enters TCL interactive shell mode") + ("p,command", "execute the commands", cxxopts::value>()) + ("m,module-file", "load the specified module (aka plugin)", cxxopts::value>()) + ("X,trace", "enable tracing of core data structure changes. for debugging") + ("M,randomize-pointers", "will slightly randomize allocated pointer addresses. for debugging") + ("A,abort", "will call abort() at the end of the script. for debugging") + ("r,top-module", "elaborate command line arguments using the specified top module", cxxopts::value()) + ("D,define", "set the specified Verilog define", cxxopts::value>()) + ("P,dump-design", "dump the design when printing the specified log header to a file", cxxopts::value>()) + ("W,warning-as-warning", "print a warning for all log messages matching the regex", cxxopts::value>()) + ("w,warning-as-message", "if a warning message matches the regex, it is printed as regular message instead", cxxopts::value>()) + ("e,warning-as-error", "if a warning message matches the regex, it is printed as error message instead", cxxopts::value>()) + ("E,deps-file", "write a Makefile dependencies file with in- and output file names", cxxopts::value()) + ("x,experimental", "do not print warnings for the specified experimental feature", cxxopts::value>()) + ("g,debug", "globally enable debug log messages") + ("V,version", "print version information and exit") + ("S,synth", "shortcut for calling the \"synth\" command") + ("perffile", "write a JSON performance log into the specified file") + ("infile", "input files", cxxopts::value>()) + ; + + options.parse_positional({"infile"}); + options.positional_help("[ [..]]"); + + // We can't have -h optionally require an argument. cxxopts is instructed to handle the no argument case + if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "-help") || !strcmp(argv[1], "--help"))) { + std::cout << options.help() << std::endl; exit(0); } + try { + // Check for "--" in arguments + auto it = std::find(argv, argv + argc, std::string("--")); + if (it != argv + argc) { + special_args.assign(it + 1, argv + argc); + // Remove these arguments from cxxopts parsing + argc = std::distance(argv, it); + } - if (argc == 2 && (!strcmp(argv[1], "-V") || !strcmp(argv[1], "-version") || !strcmp(argv[1], "--version"))) - { - printf("%s\n", yosys_version_str); - exit(0); - } + auto result = options.parse(argc, argv); - int opt; - while ((opt = getopt(argc, argv, "MXAQTVCSgm:f:Hh:b:o:p:l:L:qv:tds:c:W:w:e:r:D:P:E:x:B:")) != -1) - { - switch (opt) - { - case 'M': - memhasher_on(); - break; - case 'X': - yosys_xtrace++; - break; - case 'A': - call_abort = true; - break; - case 'Q': - print_banner = false; - break; - case 'T': - print_stats = false; - break; - case 'V': - printf("%s\n", yosys_version_str); + if (result.count("M")) memhasher_on(); + if (result.count("X")) yosys_xtrace++; + if (result.count("A")) call_abort = true; + if (result.count("Q")) print_banner = false; + if (result.count("T")) print_stats = false; + if (result.count("V")) { + std::cout << yosys_version_str << std::endl; exit(0); - case 'S': + } + if (result.count("S")) { passes_commands.push_back("synth"); run_shell = false; - break; - case 'g': - log_force_debug++; - break; - case 'm': - plugin_filenames.push_back(optarg); - break; - case 'f': - frontend_command = optarg; - break; - case 'H': + } + if (result.count("C")) run_tcl_shell = true; + if (result.count("g")) log_force_debug++; + if (result.count("m")) plugin_filenames = result["m"].as>(); + if (result.count("f")) frontend_command = result["f"].as(); + if (result.count("H")) { passes_commands.push_back("help"); run_shell = false; - break; - case 'h': - passes_commands.push_back(stringf("help %s", optarg)); + } + if (result.count("h")) { + std::string res = result["h"].as(); + passes_commands.push_back("help " + res); run_shell = false; - break; - case 'b': - backend_command = optarg; + } + if (result.count("b")) { + backend_command = result["b"].as(); run_shell = false; - break; - case 'p': - passes_commands.push_back(optarg); + } + if (result.count("p")) { + auto cmds = result["p"].as>(); + passes_commands.insert(passes_commands.end(), cmds.begin(), cmds.end()); run_shell = false; - break; - case 'o': - output_filename = optarg; + } + if (result.count("o")) { + output_filename = result["o"].as(); run_shell = false; - break; - case 'l': - case 'L': - log_files.push_back(fopen(optarg, "wt")); - if (log_files.back() == NULL) { - fprintf(stderr, "Can't open log file `%s' for writing!\n", optarg); - exit(1); + } + for (const auto& key : {"l", "L"}) { + if (result.count(key)) { + for (const auto& filename : result[key].as>()) { + if (FILE* f = fopen(filename.c_str(), "wt")) { + log_files.push_back(f); + if (key[0] == 'L') setvbuf(f, NULL, _IOLBF, 0); + } else { + std::cerr << "Can't open log file `" << filename << "' for writing!\n"; + exit(1); + } + } } - if (opt == 'L') - setvbuf(log_files.back(), NULL, _IOLBF, 0); - break; - case 'q': + } + if (result.count("q")) { mode_q = true; - if (log_errfile == stderr) - log_quiet_warnings = true; + if (log_errfile == stderr) log_quiet_warnings = true; log_errfile = stderr; - break; - case 'v': + } + if (result.count("v")) { mode_v = true; log_errfile = stderr; - log_verbose_level = atoi(optarg); - break; - case 't': - log_time = true; - break; - case 'd': - timing_details = true; - break; - case 's': - scriptfile = optarg; - scriptfile_tcl = false; - run_shell = false; - break; - case 'c': - scriptfile = optarg; - scriptfile_tcl = true; - run_shell = false; - break; - case 'W': - log_warn_regexes.push_back(YS_REGEX_COMPILE(optarg)); - break; - case 'w': - log_nowarn_regexes.push_back(YS_REGEX_COMPILE(optarg)); - break; - case 'e': - log_werror_regexes.push_back(YS_REGEX_COMPILE(optarg)); - break; - case 'r': - topmodule = optarg; - break; - case 'D': - vlog_defines.push_back(optarg); - break; - case 'P': - { - auto args = split_tokens(optarg, ":"); - if (!args.empty() && args[0] == "ALL") { - if (GetSize(args) != 1) { - fprintf(stderr, "Invalid number of tokens in -D ALL.\n"); + log_verbose_level = result["v"].as(); + } + if (result.count("t")) log_time = true; + if (result.count("d")) timing_details = true; + for (const auto& key : {"s", "c"}) { + if (result.count(key)) { + scriptfile = result[key].as(); + scriptfile_tcl = key == "c"; + run_shell = false; + } + } + for (const auto& key : {"W", "w", "e"}) { + if (result.count(key)) { + auto regexes = result[key].as>(); + for (const auto& regex : regexes) { + if (key == "W") log_warn_regexes.push_back(std::regex(regex)); + if (key == "w") log_nowarn_regexes.push_back(std::regex(regex)); + if (key == "e") log_werror_regexes.push_back(std::regex(regex)); + } + } + } + if (result.count("r")) topmodule = result["r"].as(); + if (result.count("D")) vlog_defines = result["D"].as>(); + if (result.count("P")) { + auto dump_args = result["P"].as>(); + for (const auto& arg : dump_args) { + auto tokens = split_tokens(arg, ":"); + if (!tokens.empty() && tokens[0] == "ALL") { + if (tokens.size() != 1) { + std::cerr << "Invalid number of tokens in -P ALL." << std::endl; exit(1); } log_hdump_all = true; } else { - if (!args.empty() && !args[0].empty() && args[0].back() == '.') - args[0].pop_back(); - if (GetSize(args) == 1) - args.push_back("yosys_dump_" + args[0] + ".il"); - if (GetSize(args) != 2) { - fprintf(stderr, "Invalid number of tokens in -D.\n"); + if (!tokens.empty() && !tokens[0].empty() && tokens[0].back() == '.') + tokens[0].pop_back(); + if (tokens.size() == 1) + tokens.push_back("yosys_dump_" + tokens[0] + ".il"); + if (tokens.size() != 2) { + std::cerr << "Invalid number of tokens in -P." << std::endl; exit(1); } - log_hdump[args[0]].insert(args[1]); + log_hdump[tokens[0]].insert(tokens[1]); } } - break; - case 'E': - depsfile = optarg; - break; - case 'x': - log_experimentals_ignored.insert(optarg); - break; - case 'B': - perffile = optarg; - break; - case 'C': - run_tcl_shell = true; - break; - case '\001': - frontend_files.push_back(optarg); - break; - default: - fprintf(stderr, "Run '%s -h' for help.\n", argv[0]); - exit(1); } - } + if (result.count("E")) depsfile = result["E"].as(); + if (result.count("x")) { + auto ignores = result["x"].as>(); + log_experimentals_ignored.insert(ignores.begin(), ignores.end()); + } + if (result.count("perffile")) perffile = result["perffile"].as(); + if (result.count("infile")) { + frontend_files = result["infile"].as>(); + } - if (log_errfile == NULL) { - log_files.push_back(stdout); - log_error_stderr = true; - } + if (log_errfile == NULL) { + log_files.push_back(stdout); + log_error_stderr = true; + } - if (print_banner) - yosys_banner(); + if (print_banner) + yosys_banner(); + + } + catch (const cxxopts::exceptions::parsing& e) { + std::cerr << "Error parsing options: " << e.what() << std::endl; + std::cerr << "Run '" << argv[0] << " --help' for help." << std::endl; + exit(1); + } #if defined(YOSYS_ENABLE_READLINE) || defined(YOSYS_ENABLE_EDITLINE) std::string state_dir; @@ -609,8 +465,8 @@ int main(int argc, char **argv) if (scriptfile.empty() || !scriptfile_tcl) { // Without a TCL script, arguments following '--' are also treated as frontend files - for (int i = optind; i < argc; ++i) - frontend_files.push_back(argv[i]); + for (auto special_arg : special_args) + frontend_files.push_back(special_arg); } for (auto it = frontend_files.begin(); it != frontend_files.end(); ++it) { @@ -785,10 +641,10 @@ int main(int argc, char **argv) for (auto it = timedat.rbegin(); it != timedat.rend(); it++) { if (!first) fprintf(f, ","); - fprintf(f, "\n \"%s\": {\n", std::get<2>(*it).c_str()); - fprintf(f, " \"runtime_ns\": %" PRIu64 ",\n", std::get<0>(*it)); - fprintf(f, " \"num_calls\": %u\n", std::get<1>(*it)); - fprintf(f, " }"); + fprintf(f, "\n \"%s\": {\n", std::get<2>(*it).c_str()); + fprintf(f, " \"runtime_ns\": %" PRIu64 ",\n", std::get<0>(*it)); + fprintf(f, " \"num_calls\": %u\n", std::get<1>(*it)); + fprintf(f, " }"); first = false; } fprintf(f, "\n }\n}\n"); diff --git a/libs/cxxopts b/libs/cxxopts new file mode 160000 index 00000000000..4bf61f08697 --- /dev/null +++ b/libs/cxxopts @@ -0,0 +1 @@ +Subproject commit 4bf61f08697b110d9e3991864650a405b3dd515d