/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf * * 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/yosys.h" #include "libs/sha1/sha1.h" #ifdef YOSYS_ENABLE_READLINE # include # include #endif #ifdef YOSYS_ENABLE_EDITLINE # include #endif #include #include #include #include #ifndef __STDC_FORMAT_MACROS # define __STDC_FORMAT_MACROS #endif #include #if defined (__linux__) || defined(__FreeBSD__) # include # include # include #endif #ifdef __FreeBSD__ # include # include #endif #if !defined(_WIN32) || defined(__MINGW32__) # include #endif 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 # include extern "C" int main(int, char**); extern "C" void run(const char*); extern "C" const char *errmsg(); extern "C" const char *prompt(); int main(int argc, char **argv) { EM_ASM( if (ENVIRONMENT_IS_NODE) { FS.mkdir('/hostcwd'); FS.mount(NODEFS, { root: '.' }, '/hostcwd'); FS.mkdir('/hostfs'); FS.mount(NODEFS, { root: '/' }, '/hostfs'); } ); mkdir("/work", 0777); chdir("/work"); log_files.push_back(stdout); log_error_stderr = true; yosys_banner(); yosys_setup(); #ifdef WITH_PYTHON PyRun_SimpleString(("sys.path.append(\""+proc_self_dirname()+"\")").c_str()); PyRun_SimpleString(("sys.path.append(\""+proc_share_dirname()+"plugins\")").c_str()); #endif if (argc == 2) { // Run the first argument as a script file run_frontend(argv[1], "script"); } } void run(const char *command) { int selSize = GetSize(yosys_get_design()->selection_stack); try { log_last_error = "Internal error (see JavaScript console for details)"; run_pass(command); log_last_error = ""; } catch (...) { while (GetSize(yosys_get_design()->selection_stack) > selSize) yosys_get_design()->selection_stack.pop_back(); throw; } } const char *errmsg() { return log_last_error.c_str(); } const char *prompt() { const char *p = create_prompt(yosys_get_design(), 0); while (*p == '\n') p++; return p; } #else /* EMSCRIPTEN */ #if defined(YOSYS_ENABLE_READLINE) || defined(YOSYS_ENABLE_EDITLINE) int yosys_history_offset = 0; std::string yosys_history_file; #endif #if defined(__wasm) extern "C" { // FIXME: WASI does not currently support exceptions. void* __cxa_allocate_exception(size_t thrown_size) throw() { return malloc(thrown_size); } bool __cxa_uncaught_exception() throw(); void __cxa_throw(void* thrown_exception, struct std::type_info * tinfo, void (*dest)(void*)) { std::terminate(); } } #endif void yosys_atexit() { #if defined(YOSYS_ENABLE_READLINE) || defined(YOSYS_ENABLE_EDITLINE) if (!yosys_history_file.empty()) { #if defined(YOSYS_ENABLE_READLINE) if (yosys_history_offset > 0) { history_truncate_file(yosys_history_file.c_str(), 100); append_history(where_history() - yosys_history_offset, yosys_history_file.c_str()); } else write_history(yosys_history_file.c_str()); #else write_history(yosys_history_file.c_str()); #endif } clear_history(); #if defined(YOSYS_ENABLE_READLINE) HIST_ENTRY **hist_list = history_list(); if (hist_list != NULL) free(hist_list); #endif #endif } #if defined(__OpenBSD__) namespace Yosys { extern char *yosys_argv0; extern char yosys_path[PATH_MAX]; }; #endif #ifdef YOSYS_ENABLE_TCL namespace Yosys { extern int yosys_tcl_iterp_init(Tcl_Interp *interp); extern void yosys_tcl_activate_repl(); }; #endif int main(int argc, char **argv) { std::string frontend_command = "auto"; std::string backend_command = "auto"; std::vector vlog_defines; std::vector passes_commands; std::vector frontend_files; std::vector plugin_filenames; std::string output_filename = ""; std::string scriptfile = ""; std::string depsfile = ""; std::string topmodule = ""; std::string perffile = ""; bool scriptfile_tcl = false; bool print_banner = true; bool print_stats = true; bool call_abort = false; bool timing_details = false; bool run_shell = true; bool run_tcl_shell = false; 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 an 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"); exit(0); } if (argc == 2 && (!strcmp(argv[1], "-V") || !strcmp(argv[1], "-version") || !strcmp(argv[1], "--version"))) { printf("%s\n", yosys_version_str); exit(0); } 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); exit(0); case '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': passes_commands.push_back("help"); run_shell = false; break; case 'h': passes_commands.push_back(stringf("help %s", optarg)); run_shell = false; break; case 'b': backend_command = optarg; run_shell = false; break; case 'p': passes_commands.push_back(optarg); run_shell = false; break; case 'o': output_filename = optarg; 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); } if (opt == 'L') setvbuf(log_files.back(), NULL, _IOLBF, 0); break; case 'q': mode_q = true; if (log_errfile == stderr) log_quiet_warnings = true; log_errfile = stderr; break; case '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"); 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"); exit(1); } log_hdump[args[0]].insert(args[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 (log_errfile == NULL) { log_files.push_back(stdout); log_error_stderr = true; } if (print_banner) yosys_banner(); #if defined(YOSYS_ENABLE_READLINE) || defined(YOSYS_ENABLE_EDITLINE) std::string state_dir; #if defined(_WIN32) if (getenv("HOMEDRIVE") != NULL && getenv("HOMEPATH") != NULL) { state_dir = stringf("%s%s/.local/state", getenv("HOMEDRIVE"), getenv("HOMEPATH")); } else { log_debug("$HOMEDRIVE and/or $HOMEPATH is empty. No history file will be created.\n"); } #else if (getenv("XDG_STATE_HOME") == NULL || getenv("XDG_STATE_HOME")[0] == '\0') { if (getenv("HOME") != NULL) { state_dir = stringf("%s/.local/state", getenv("HOME")); } else { log_debug("$HOME is empty. No history file will be created.\n"); } } else { state_dir = stringf("%s", getenv("XDG_STATE_HOME")); } #endif if (!state_dir.empty()) { std::string yosys_dir = state_dir + "/yosys"; create_directory(yosys_dir); yosys_history_file = yosys_dir + "/history"; read_history(yosys_history_file.c_str()); yosys_history_offset = where_history(); } #endif if (print_stats) log_hasher = new SHA1; #if defined(__OpenBSD__) // save the executable origin for proc_self_dirname() yosys_argv0 = argv[0]; realpath(yosys_argv0, yosys_path); #endif #if defined(__linux__) // set stack size to >= 128 MB { struct rlimit rl; const rlim_t stack_size = 128L * 1024L * 1024L; if (getrlimit(RLIMIT_STACK, &rl) == 0 && rl.rlim_cur < stack_size) { rl.rlim_cur = stack_size; setrlimit(RLIMIT_STACK, &rl); } } #endif yosys_setup(); #ifdef WITH_PYTHON PyRun_SimpleString(("sys.path.append(\""+proc_self_dirname()+"\")").c_str()); PyRun_SimpleString(("sys.path.append(\""+proc_share_dirname()+"plugins\")").c_str()); #endif log_error_atexit = yosys_atexit; for (auto &fn : plugin_filenames) load_plugin(fn, {}); log_suppressed(); if (!vlog_defines.empty()) { std::string vdef_cmd = "read -define"; for (auto vdef : vlog_defines) vdef_cmd += " " + vdef; run_pass(vdef_cmd); } 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 it = frontend_files.begin(); it != frontend_files.end(); ++it) { if (run_frontend((*it).c_str(), frontend_command)) run_shell = false; } if (!topmodule.empty()) run_pass("hierarchy -top " + topmodule); if (!scriptfile.empty()) { if (scriptfile_tcl) { #ifdef YOSYS_ENABLE_TCL int tcl_argc = argc - optind; std::vector script_args; Tcl_Interp *interp = yosys_get_tcl_interp(); for (int i = optind; i < argc; ++i) script_args.push_back(Tcl_NewStringObj(argv[i], strlen(argv[i]))); Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argc", 4), NULL, Tcl_NewIntObj(tcl_argc), 0); Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv", 4), NULL, Tcl_NewListObj(tcl_argc, script_args.data()), 0); Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv0", 5), NULL, Tcl_NewStringObj(scriptfile.c_str(), scriptfile.length()), 0); if (Tcl_EvalFile(interp, scriptfile.c_str()) != TCL_OK) log_error("TCL interpreter returned an error: %s\n", Tcl_GetStringResult(yosys_get_tcl_interp())); #else log_error("Can't exectue TCL script: this version of yosys is not built with TCL support enabled.\n"); #endif } else run_frontend(scriptfile, "script"); } for (auto it = passes_commands.begin(); it != passes_commands.end(); it++) run_pass(*it); 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"); #endif } else { if (run_shell) shell(yosys_design); else run_backend(output_filename, backend_command); } yosys_design->check(); for (auto it : saved_designs) it.second->check(); for (auto it : pushed_designs) it->check(); if (!depsfile.empty()) { FILE *f = fopen(depsfile.c_str(), "wt"); if (f == nullptr) log_error("Can't open dependencies file for writing: %s\n", strerror(errno)); bool first = true; for (auto fn : yosys_output_files) { fprintf(f, "%s%s", first ? "" : " ", escape_filename_spaces(fn).c_str()); first = false; } fprintf(f, ":"); for (auto fn : yosys_input_files) { if (yosys_output_files.count(fn) == 0) fprintf(f, " %s", escape_filename_spaces(fn).c_str()); } fprintf(f, "\n"); } if (log_expect_no_warnings && log_warnings_count_noexpect) log_error("Unexpected warnings found: %d unique messages, %d total, %d expected\n", GetSize(log_warnings), log_warnings_count, log_warnings_count - log_warnings_count_noexpect); if (print_stats) { std::string hash = log_hasher->final().substr(0, 10); delete log_hasher; log_hasher = nullptr; log_time = false; yosys_xtrace = 0; log_spacer(); if (mode_v && !mode_q) log_files.push_back(stderr); if (log_warnings_count) log("Warnings: %d unique messages, %d total\n", GetSize(log_warnings), log_warnings_count); if (!log_experimentals.empty()) log("Warnings: %d experimental features used (not excluded with -x).\n", GetSize(log_experimentals)); #ifdef _WIN32 log("End of script. Logfile hash: %s\n", hash.c_str()); #else std::string meminfo; std::string stats_divider = ", "; struct rusage ru_buffer; getrusage(RUSAGE_SELF, &ru_buffer); if (yosys_design->scratchpad_get_bool("print_stats.include_children")) { struct rusage ru_buffer_children; getrusage(RUSAGE_CHILDREN, &ru_buffer_children); ru_buffer.ru_utime.tv_sec += ru_buffer_children.ru_utime.tv_sec; ru_buffer.ru_utime.tv_usec += ru_buffer_children.ru_utime.tv_usec; ru_buffer.ru_stime.tv_sec += ru_buffer_children.ru_stime.tv_sec; ru_buffer.ru_stime.tv_usec += ru_buffer_children.ru_stime.tv_usec; #if defined(__linux__) || defined(__FreeBSD__) ru_buffer.ru_maxrss = std::max(ru_buffer.ru_maxrss, ru_buffer_children.ru_maxrss); #endif } #if defined(__linux__) || defined(__FreeBSD__) meminfo = stringf(", MEM: %.2f MB peak", ru_buffer.ru_maxrss / 1024.0); #endif log("End of script. Logfile hash: %s%sCPU: user %.2fs system %.2fs%s\n", hash.c_str(), stats_divider.c_str(), ru_buffer.ru_utime.tv_sec + 1e-6 * ru_buffer.ru_utime.tv_usec, ru_buffer.ru_stime.tv_sec + 1e-6 * ru_buffer.ru_stime.tv_usec, meminfo.c_str()); #endif log("%s\n", yosys_version_str); int64_t total_ns = 0; std::set> timedat; for (auto &it : pass_register) if (it.second->call_counter) { total_ns += it.second->runtime_ns + 1; timedat.insert(make_tuple(it.second->runtime_ns + 1, it.second->call_counter, it.first)); } if (timing_details) { log("Time spent:\n"); for (auto it = timedat.rbegin(); it != timedat.rend(); it++) { log("%5d%% %5d calls %8.3f sec %s\n", int(100*std::get<0>(*it) / total_ns), std::get<1>(*it), std::get<0>(*it) / 1000000000.0, std::get<2>(*it).c_str()); } } else { int out_count = 0; log("Time spent:"); for (auto it = timedat.rbegin(); it != timedat.rend() && out_count < 4; it++, out_count++) { if (out_count >= 2 && (std::get<0>(*it) < 1000000000 || int(100*std::get<0>(*it) / total_ns) < 20)) { log(", ..."); break; } log("%s %d%% %dx %s (%d sec)", out_count ? "," : "", int(100*std::get<0>(*it) / total_ns), std::get<1>(*it), std::get<2>(*it).c_str(), int(std::get<0>(*it) / 1000000000)); } log("%s\n", out_count ? "" : " no commands executed"); } if(!perffile.empty()) { FILE *f = fopen(perffile.c_str(), "wt"); if (f == nullptr) log_error("Can't open performance log file for writing: %s\n", strerror(errno)); fprintf(f, "{\n"); fprintf(f, " \"generator\": \"%s\",\n", yosys_version_str); fprintf(f, " \"total_ns\": %" PRIu64 ",\n", total_ns); fprintf(f, " \"passes\": {"); bool first = true; 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, " }"); first = false; } fprintf(f, "\n }\n}\n"); } } #if defined(YOSYS_ENABLE_COVER) && (defined(__linux__) || defined(__FreeBSD__)) if (getenv("YOSYS_COVER_DIR") || getenv("YOSYS_COVER_FILE")) { string filename; FILE *f; if (getenv("YOSYS_COVER_DIR")) { filename = stringf("%s/yosys_cover_%d_XXXXXX.txt", getenv("YOSYS_COVER_DIR"), getpid()); filename = make_temp_file(filename); } else { filename = getenv("YOSYS_COVER_FILE"); } f = fopen(filename.c_str(), "a+"); if (f == NULL) log_error("Can't create coverage file `%s'.\n", filename.c_str()); log("\n", filename.c_str()); for (auto &it : get_coverage_data()) fprintf(f, "%-60s %10d %s\n", it.second.first.c_str(), it.second.second, it.first.c_str()); fclose(f); } #endif log_check_expected(); yosys_atexit(); memhasher_off(); if (call_abort) abort(); log_flush(); #if defined(_MSC_VER) _exit(0); #elif defined(_WIN32) _Exit(0); #endif yosys_shutdown(); return 0; } #endif /* EMSCRIPTEN */