/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Clifford Wolf * Copyright (C) 2018 Miodrag Milanovic * * 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 NO_GUI #include #include "application.h" #include "mainwindow.h" #endif #ifndef NO_PYTHON #include "pybindings.h" #endif #include #include #include #include #include #include #include "command.h" #include "design_utils.h" #include "json_frontend.h" #include "jsonwrite.h" #include "log.h" #include "timing.h" #include "util.h" #include "version.h" NEXTPNR_NAMESPACE_BEGIN CommandHandler::CommandHandler(int argc, char **argv) : argc(argc), argv(argv) { log_streams.clear(); } bool CommandHandler::parseOptions() { options.add(getGeneralOptions()).add(getArchOptions()); try { po::parsed_options parsed = po::command_line_parser(argc, argv) .style(po::command_line_style::default_style ^ po::command_line_style::allow_guessing) .options(options) .positional(pos) .run(); po::store(parsed, vm); po::notify(vm); return true; } catch (std::exception &e) { std::cout << e.what() << "\n"; return false; } } bool CommandHandler::executeBeforeContext() { if (vm.count("help") || argc == 1) { std::cerr << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (Version " GIT_DESCRIBE_STR ")\n"; std::cerr << options << "\n"; return argc != 1; } if (vm.count("version")) { std::cerr << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (Version " GIT_DESCRIBE_STR ")\n"; return true; } validate(); if (vm.count("quiet")) { log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING_MSG)); } else { log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG_MSG)); } if (vm.count("log")) { std::string logfilename = vm["log"].as(); logfile.open(logfilename); if (!logfile.is_open()) log_error("Failed to open log file '%s' for writing.\n", logfilename.c_str()); log_streams.push_back(std::make_pair(&logfile, LogLevel::LOG_MSG)); } return false; } po::options_description CommandHandler::getGeneralOptions() { po::options_description general("General options"); general.add_options()("help,h", "show help"); general.add_options()("verbose,v", "verbose output"); general.add_options()("quiet,q", "quiet mode, only errors and warnings displayed"); general.add_options()("log,l", po::value(), "log file, all log messages are written to this file regardless of -q"); general.add_options()("debug", "debug output"); general.add_options()("force,f", "keep running after errors"); #ifndef NO_GUI general.add_options()("gui", "start gui"); general.add_options()("gui-no-aa", "disable anti aliasing (use together with --gui option)"); #endif #ifndef NO_PYTHON general.add_options()("run", po::value>(), "python file to execute instead of default flow"); pos.add("run", -1); general.add_options()("pre-pack", po::value>(), "python file to run before packing"); general.add_options()("pre-place", po::value>(), "python file to run before placement"); general.add_options()("pre-route", po::value>(), "python file to run before routing"); general.add_options()("post-route", po::value>(), "python file to run after routing"); #endif general.add_options()("json", po::value(), "JSON design file to ingest"); general.add_options()("write", po::value(), "JSON design file to write"); general.add_options()("top", po::value(), "name of top module"); general.add_options()("seed", po::value(), "seed value for random number generator"); general.add_options()("randomize-seed,r", "randomize seed value for random number generator"); general.add_options()( "placer", po::value(), std::string("placer algorithm to use; available: " + boost::algorithm::join(Arch::availablePlacers, ", ") + "; default: " + Arch::defaultPlacer) .c_str()); general.add_options()( "router", po::value(), std::string("router algorithm to use; available: " + boost::algorithm::join(Arch::availableRouters, ", ") + "; default: " + Arch::defaultRouter) .c_str()); general.add_options()("slack_redist_iter", po::value(), "number of iterations between slack redistribution"); general.add_options()("cstrweight", po::value(), "placer weighting for relative constraint satisfaction"); general.add_options()("starttemp", po::value(), "placer SA start temperature"); general.add_options()("placer-budgets", "use budget rather than criticality in placer timing weights"); general.add_options()("pack-only", "pack design only without placement or routing"); general.add_options()("no-route", "process design without routing"); general.add_options()("no-place", "process design without placement"); general.add_options()("no-pack", "process design without packing"); general.add_options()("ignore-loops", "ignore combinational loops in timing analysis"); general.add_options()("version,V", "show version"); general.add_options()("test", "check architecture database integrity"); general.add_options()("freq", po::value(), "set target frequency for design in MHz"); general.add_options()("timing-allow-fail", "allow timing to fail in design"); general.add_options()("no-tmdriv", "disable timing-driven placement"); general.add_options()("sdf", po::value(), "SDF delay back-annotation file to write"); general.add_options()("sdf-cvc", "enable tweaks for SDF file compatibility with the CVC simulator"); general.add_options()("no-print-critical-path-source", "disable printing of the line numbers associated with each net in the critical path"); general.add_options()("placed-svg", po::value(), "write render of placement to SVG file"); general.add_options()("routed-svg", po::value(), "write render of routing to SVG file"); return general; } void CommandHandler::setupContext(Context *ctx) { if (ctx->settings.find(ctx->id("seed")) != ctx->settings.end()) ctx->rngstate = ctx->setting("seed"); if (vm.count("verbose")) { ctx->verbose = true; } if (vm.count("debug")) { ctx->verbose = true; ctx->debug = true; } if (vm.count("no-print-critical-path-source")) { ctx->disable_critical_path_source_print = true; } if (vm.count("force")) { ctx->force = true; } if (vm.count("seed")) { ctx->rngseed(vm["seed"].as()); } if (vm.count("randomize-seed")) { srand(time(NULL)); int r; do { r = rand(); } while (r == 0); ctx->rngseed(r); } if (vm.count("slack_redist_iter")) { ctx->settings[ctx->id("slack_redist_iter")] = vm["slack_redist_iter"].as(); if (vm.count("freq") && vm["freq"].as() == 0) { ctx->settings[ctx->id("auto_freq")] = true; #ifndef NO_GUI if (!vm.count("gui")) #endif log_warning("Target frequency not specified. Will optimise for max frequency.\n"); } } if (vm.count("ignore-loops")) { ctx->settings[ctx->id("timing/ignoreLoops")] = true; } if (vm.count("timing-allow-fail")) { ctx->settings[ctx->id("timing/allowFail")] = true; } if (vm.count("placer")) { std::string placer = vm["placer"].as(); if (std::find(Arch::availablePlacers.begin(), Arch::availablePlacers.end(), placer) == Arch::availablePlacers.end()) log_error("Placer algorithm '%s' is not supported (available options: %s)\n", placer.c_str(), boost::algorithm::join(Arch::availablePlacers, ", ").c_str()); ctx->settings[ctx->id("placer")] = placer; } if (vm.count("router")) { std::string router = vm["router"].as(); if (std::find(Arch::availableRouters.begin(), Arch::availableRouters.end(), router) == Arch::availableRouters.end()) log_error("Router algorithm '%s' is not supported (available options: %s)\n", router.c_str(), boost::algorithm::join(Arch::availableRouters, ", ").c_str()); ctx->settings[ctx->id("router")] = router; } if (vm.count("cstrweight")) { ctx->settings[ctx->id("placer1/constraintWeight")] = std::to_string(vm["cstrweight"].as()); } if (vm.count("starttemp")) { ctx->settings[ctx->id("placer1/startTemp")] = std::to_string(vm["starttemp"].as()); } if (vm.count("placer-budgets")) { ctx->settings[ctx->id("placer1/budgetBased")] = true; } if (vm.count("freq")) { auto freq = vm["freq"].as(); if (freq > 0) ctx->settings[ctx->id("target_freq")] = std::to_string(freq * 1e6); } if (vm.count("no-tmdriv")) ctx->settings[ctx->id("timing_driven")] = false; // Setting default values if (ctx->settings.find(ctx->id("target_freq")) == ctx->settings.end()) ctx->settings[ctx->id("target_freq")] = std::to_string(12e6); if (ctx->settings.find(ctx->id("timing_driven")) == ctx->settings.end()) ctx->settings[ctx->id("timing_driven")] = true; if (ctx->settings.find(ctx->id("slack_redist_iter")) == ctx->settings.end()) ctx->settings[ctx->id("slack_redist_iter")] = 0; if (ctx->settings.find(ctx->id("auto_freq")) == ctx->settings.end()) ctx->settings[ctx->id("auto_freq")] = false; if (ctx->settings.find(ctx->id("placer")) == ctx->settings.end()) ctx->settings[ctx->id("placer")] = Arch::defaultPlacer; if (ctx->settings.find(ctx->id("router")) == ctx->settings.end()) ctx->settings[ctx->id("router")] = Arch::defaultRouter; ctx->settings[ctx->id("arch.name")] = std::string(ctx->archId().c_str(ctx)); ctx->settings[ctx->id("arch.type")] = std::string(ctx->archArgsToId(ctx->archArgs()).c_str(ctx)); ctx->settings[ctx->id("seed")] = ctx->rngstate; } int CommandHandler::executeMain(std::unique_ptr ctx) { if (vm.count("test")) { ctx->archcheck(); return 0; } if (vm.count("top")) { ctx->settings[ctx->id("frontend/top")] = vm["top"].as(); } #ifndef NO_GUI if (vm.count("gui")) { Application a(argc, argv, (vm.count("gui-no-aa") > 0)); MainWindow w(std::move(ctx), this); try { if (vm.count("json")) { std::string filename = vm["json"].as(); std::ifstream f(filename); if (!parse_json(f, filename, w.getContext())) log_error("Loading design failed.\n"); customAfterLoad(w.getContext()); w.notifyChangeContext(); w.updateActions(); } else w.notifyChangeContext(); } catch (log_execution_error_exception) { // show error is handled by gui itself } w.show(); return a.exec(); } #endif if (vm.count("json")) { std::string filename = vm["json"].as(); std::ifstream f(filename); if (!parse_json(f, filename, ctx.get())) log_error("Loading design failed.\n"); customAfterLoad(ctx.get()); } #ifndef NO_PYTHON init_python(argv[0]); python_export_global("ctx", *ctx); if (vm.count("run")) { std::vector files = vm["run"].as>(); for (auto filename : files) execute_python_file(filename.c_str()); } else #endif if (ctx->design_loaded) { bool do_pack = vm.count("pack-only") != 0 || vm.count("no-pack") == 0; bool do_place = vm.count("pack-only") == 0 && vm.count("no-place") == 0; bool do_route = vm.count("pack-only") == 0 && vm.count("no-route") == 0; if (do_pack) { run_script_hook("pre-pack"); if (!ctx->pack() && !ctx->force) log_error("Packing design failed.\n"); } assign_budget(ctx.get()); ctx->check(); print_utilisation(ctx.get()); if (do_place) { run_script_hook("pre-place"); if (!ctx->place() && !ctx->force) log_error("Placing design failed.\n"); ctx->check(); if (vm.count("placed-svg")) ctx->writeSVG(vm["placed-svg"].as(), "scale=50 hide_routing"); } if (do_route) { run_script_hook("pre-route"); if (!ctx->route() && !ctx->force) log_error("Routing design failed.\n"); run_script_hook("post-route"); if (vm.count("routed-svg")) ctx->writeSVG(vm["routed-svg"].as(), "scale=500"); } customBitstream(ctx.get()); } if (vm.count("write")) { std::string filename = vm["write"].as(); std::ofstream f(filename); if (!write_json_file(f, filename, ctx.get())) log_error("Saving design failed.\n"); } if (vm.count("sdf")) { std::string filename = vm["sdf"].as(); std::ofstream f(filename); if (!f) log_error("Failed to open SDF file '%s' for writing.\n", filename.c_str()); ctx->writeSDF(f, vm.count("sdf-cvc")); } #ifndef NO_PYTHON deinit_python(); #endif return had_nonfatal_error ? 1 : 0; } void CommandHandler::conflicting_options(const boost::program_options::variables_map &vm, const char *opt1, const char *opt2) { if (vm.count(opt1) && !vm[opt1].defaulted() && vm.count(opt2) && !vm[opt2].defaulted()) { std::string msg = "Conflicting options '" + std::string(opt1) + "' and '" + std::string(opt2) + "'."; log_error("%s\n", msg.c_str()); } } void CommandHandler::printFooter() { int warning_count = get_or_default(message_count_by_level, LogLevel::WARNING_MSG, 0), error_count = get_or_default(message_count_by_level, LogLevel::ERROR_MSG, 0); if (warning_count > 0 || error_count > 0) log_always("%d warning%s, %d error%s\n", warning_count, warning_count == 1 ? "" : "s", error_count, error_count == 1 ? "" : "s"); } int CommandHandler::exec() { try { if (!parseOptions()) return -1; if (executeBeforeContext()) return 0; std::unordered_map values; std::unique_ptr ctx = createContext(values); setupContext(ctx.get()); setupArchContext(ctx.get()); int rc = executeMain(std::move(ctx)); printFooter(); return rc; } catch (log_execution_error_exception) { printFooter(); return -1; } } std::unique_ptr CommandHandler::load_json(std::string filename) { std::unordered_map values; std::unique_ptr ctx = createContext(values); setupContext(ctx.get()); setupArchContext(ctx.get()); { std::ifstream f(filename); if (!parse_json(f, filename, ctx.get())) log_error("Loading design failed.\n"); } customAfterLoad(ctx.get()); return ctx; } void CommandHandler::clear() { vm.clear(); } void CommandHandler::run_script_hook(const std::string &name) { #ifndef NO_PYTHON if (vm.count(name)) { std::vector files = vm[name].as>(); for (auto filename : files) execute_python_file(filename.c_str()); } #endif } NEXTPNR_NAMESPACE_END EGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * --- * * The Verilog frontend. * * This frontend is using the AST frontend library (see frontends/ast/). * Thus this frontend does not generate RTLIL code directly but creates an * AST directly from the Verilog parse tree and then passes this AST to * the AST frontend library. * * --- * * This file contains an ad-hoc parser for Verilog constants. The Verilog * lexer does only recognize a constant but does not actually split it to its * components. I.e. it just passes the Verilog code for the constant to the * bison parser. The parser then uses the function const2ast() from this file * to create an AST node for the constant. * */ #include "verilog_frontend.h" #include "kernel/log.h" #include <string.h> #include <math.h> YOSYS_NAMESPACE_BEGIN using namespace AST; // divide an arbitrary length decimal number by two and return the rest static int my_decimal_div_by_two(std::vector<uint8_t> &digits) { int carry = 0; for (size_t i = 0; i < digits.size(); i++) { if (digits[i] >= 10) log_error("Invalid use of [a-fxz?] in decimal constant at %s:%d.\n", current_filename.c_str(), get_line_num()); digits[i] += carry * 10; carry = digits[i] % 2; digits[i] /= 2; } while (!digits.empty() && !digits.front()) digits.erase(digits.begin()); return carry; } // find the number of significant bits in a binary number (not including the sign bit) static int my_ilog2(int x) { int ret = 0; while (x != 0 && x != -1) { x = x >> 1; ret++; } return ret; } // parse a binary, decimal, hexadecimal or octal number with support for special bits ('x', 'z' and '?') static void my_strtobin(std::vector<RTLIL::State> &data, const char *str, int len_in_bits, int base, char case_type) { // all digits in string (MSB at index 0) std::vector<uint8_t> digits; while (*str) { if ('0' <= *str && *str <= '9') digits.push_back(*str - '0'); else if ('a' <= *str && *str <= 'f') digits.push_back(10 + *str - 'a'); else if ('A' <= *str && *str <= 'F') digits.push_back(10 + *str - 'A'); else if (*str == 'x' || *str == 'X') digits.push_back(0xf0); else if (*str == 'z' || *str == 'Z') digits.push_back(0xf1); else if (*str == '?') digits.push_back(0xf2); str++; } if (base == 10 && GetSize(digits) == 1 && digits.front() >= 0xf0) base = 2; data.clear(); if (base == 10) { while (!digits.empty()) data.push_back(my_decimal_div_by_two(digits) ? RTLIL::S1 : RTLIL::S0); } else { int bits_per_digit = my_ilog2(base-1); for (auto it = digits.rbegin(), e = digits.rend(); it != e; it++) { if (*it > (base-1) && *it < 0xf0) log_error("Digit larger than %d used in in base-%d constant at %s:%d.\n", base-1, base, current_filename.c_str(), get_line_num()); for (int i = 0; i < bits_per_digit; i++) { int bitmask = 1 << i; if (*it == 0xf0) data.push_back(case_type == 'x' ? RTLIL::Sa : RTLIL::Sx); else if (*it == 0xf1) data.push_back(case_type == 'x' || case_type == 'z' ? RTLIL::Sa : RTLIL::Sz); else if (*it == 0xf2) data.push_back(RTLIL::Sa); else data.push_back((*it & bitmask) ? RTLIL::S1 : RTLIL::S0); } } } int len = GetSize(data); RTLIL::State msb = data.empty() ? RTLIL::S0 : data.back(); if (len_in_bits < 0) { if (len < 32) data.resize(32, msb == RTLIL::S0 || msb == RTLIL::S1 ? RTLIL::S0 : msb); return; } for (len = len - 1; len >= 0; len--) if (data[len] == RTLIL::S1) break; if (msb == RTLIL::S0 || msb == RTLIL::S1) { len += 1; data.resize(len_in_bits, RTLIL::S0); } else { len += 2; data.resize(len_in_bits, msb); } if (len > len_in_bits) log_warning("Literal has a width of %d bit, but value requires %d bit. (%s:%d)\n", len_in_bits, len, current_filename.c_str(), get_line_num()); } // convert the Verilog code for a constant to an AST node AstNode *VERILOG_FRONTEND::const2ast(std::string code, char case_type, bool warn_z) { if (warn_z) { AstNode *ret = const2ast(code, case_type); if (std::find(ret->bits.begin(), ret->bits.end(), RTLIL::State::Sz) != ret->bits.end()) log_warning("Yosys has only limited support for tri-state logic at the moment. (%s:%d)\n", current_filename.c_str(), get_line_num()); return ret; } const char *str = code.c_str(); // Strings if (*str == '"') { int len = strlen(str) - 2; std::vector<RTLIL::State> data; data.reserve(len * 8); for (int i = 0; i < len; i++) { unsigned char ch = str[len - i]; for (int j = 0; j < 8; j++) { data.push_back((ch & 1) ? RTLIL::S1 : RTLIL::S0); ch = ch >> 1; } } AstNode *ast = AstNode::mkconst_bits(data, false); ast->str = code; return ast; } for (size_t i = 0; i < code.size(); i++) if (code[i] == '_' || code[i] == ' ' || code[i] == '\t' || code[i] == '\r' || code[i] == '\n') code.erase(code.begin()+(i--)); str = code.c_str(); char *endptr; long len_in_bits = strtol(str, &endptr, 10); // Simple base-10 integer if (*endptr == 0) { std::vector<RTLIL::State> data; my_strtobin(data, str, -1, 10, case_type); if (data.back() == RTLIL::S1) data.push_back(RTLIL::S0); return AstNode::mkconst_bits(data, true); } // unsized constant if (str == endptr) len_in_bits = -1; // The "<bits>'s?[bodhBODH]<digits>" syntax if (*endptr == '\'') { std::vector<RTLIL::State> data; bool is_signed = false; if (*(endptr+1) == 's') { is_signed = true; endptr++; } switch (*(endptr+1)) { case 'b': case 'B': my_strtobin(data, endptr+2, len_in_bits, 2, case_type); break; case 'o': case 'O': my_strtobin(data, endptr+2, len_in_bits, 8, case_type); break; case 'd': case 'D': my_strtobin(data, endptr+2, len_in_bits, 10, case_type); break; case 'h': case 'H': my_strtobin(data, endptr+2, len_in_bits, 16, case_type); break; default: return NULL; } if (len_in_bits < 0) { if (is_signed && data.back() == RTLIL::S1) data.push_back(RTLIL::S0); } return AstNode::mkconst_bits(data, is_signed); } return NULL; } YOSYS_NAMESPACE_END