diff --git a/CMakeLists.txt b/CMakeLists.txt index 76fc15f6..f4b3c61a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,14 +121,14 @@ if (BUILD_PYTHON) set(version ${PYTHONLIBS_VERSION_STRING}) STRING(REGEX REPLACE "[^0-9]" "" boost_py_version ${version}) - find_package(Boost COMPONENTS "python-py${boost_py_version}" ${boost_libs}) + find_package(Boost QUIET COMPONENTS "python-py${boost_py_version}" ${boost_libs}) set(Boost_PYTHON_FOUND ${Boost_PYTHON-PY${boost_py_version}_FOUND}) while (NOT "${version}" STREQUAL "" AND NOT Boost_PYTHON_FOUND) STRING(REGEX REPLACE "([0-9.]+).[0-9]+" "\\1" version ${version}) STRING(REGEX REPLACE "[^0-9]" "" boost_py_version ${version}) - find_package(Boost COMPONENTS "python-py${boost_py_version}" ${boost_libs}) + find_package(Boost QUIET COMPONENTS "python-py${boost_py_version}" ${boost_libs}) set(Boost_PYTHON_FOUND ${Boost_PYTHON-PY${boost_py_version}_FOUND}) STRING(REGEX MATCHALL "([0-9.]+).[0-9]+" has_more_version ${version}) @@ -138,21 +138,21 @@ if (BUILD_PYTHON) endwhile () if (NOT Boost_PYTHON_FOUND) - find_package(Boost COMPONENTS python3 ${boost_libs}) + find_package(Boost QUIET COMPONENTS python3 ${boost_libs}) if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" ) set(Boost_PYTHON_FOUND TRUE) endif () endif () if (NOT Boost_PYTHON_FOUND) - find_package(Boost COMPONENTS python36 ${boost_libs}) + find_package(Boost QUIET COMPONENTS python36 ${boost_libs}) if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" ) set(Boost_PYTHON_FOUND TRUE) endif () endif () if (NOT Boost_PYTHON_FOUND) - find_package(Boost COMPONENTS python37 ${boost_libs}) + find_package(Boost QUIET COMPONENTS python37 ${boost_libs}) if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" ) set(Boost_PYTHON_FOUND TRUE) endif () @@ -160,7 +160,7 @@ if (BUILD_PYTHON) if (NOT Boost_PYTHON_FOUND) STRING(REGEX REPLACE "([0-9]+\\.[0-9]+).*" "\\1" gentoo_version ${PYTHONLIBS_VERSION_STRING}) - find_package(Boost COMPONENTS python-${gentoo_version} ${boost_libs}) + find_package(Boost QUIET COMPONENTS python-${gentoo_version} ${boost_libs}) if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" ) set(Boost_PYTHON_FOUND TRUE) endif () diff --git a/README.md b/README.md index 010acd8b..86f7ef2d 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ of the selected architecture: - Qt5 or later (`qt5-default` for Ubuntu 16.04) - Python 3.5 or later, including development libraries (`python3-dev` for Ubuntu) - on Windows make sure to install same version as supported by [vcpkg](https://github.com/Microsoft/vcpkg/blob/master/ports/python3/CONTROL) -- Boost libraries (`libboost-dev` or `libboost-all-dev` for Ubuntu) +- Boost libraries (`libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-python-dev libboost-dev` or `libboost-all-dev` for Ubuntu) - Latest git Yosys is required to synthesise the demo design - For building on Windows with MSVC, usage of vcpkg is advised for dependency installation. - For 32 bit builds: `vcpkg install boost-filesystem boost-program-options boost-thread boost-python qt5-base` @@ -49,7 +49,8 @@ Getting started ### nextpnr-ice40 -To build the iCE40 version of nextpnr, install [icestorm](http://www.clifford.at/icestorm/) with chipdbs installed in `/usr/local/share/icebox`. +To build the iCE40 version of nextpnr, install [icestorm](http://www.clifford.at/icestorm/) with chipdbs installed in `/usr/local/share/icebox` +(or another location, which should be passed as -DICEBOX_ROOT=/path/to/icebox to CMake). Then build and install `nextpnr-ice40` using the following commands: ``` @@ -96,8 +97,6 @@ sudo make install - More examples of the ECP5 flow for a range of boards can be found in the [Project Trellis Examples](https://github.com/SymbiFlow/prjtrellis/tree/master/examples). - - Currently the ECP5 flow supports LUTs, flipflops and IO. IO must be instantiated using `TRELLIS_IO` primitives and constraints specified - using `LOC` and `IO_TYPE` attributes on those instances, as is used in the examples. ### nextpnr-generic @@ -116,7 +115,7 @@ Additional notes for building nextpnr Use cmake `-D` options to specify which version of nextpnr you want to build. -Use `-DARCH=...` to set the architecture. It is semicolon separated list. +Use `-DARCH=...` to set the architecture. It is a semicolon separated list. Use `cmake . -DARCH=all` to build all supported architectures. The following runs a debug build of the iCE40 architecture without GUI @@ -134,6 +133,9 @@ cmake -DARCH=ice40 -DBUILD_PYTHON=OFF -DBUILD_GUI=OFF -DSTATIC_BUILD=ON . make -j$(nproc) ``` +You can change the location where nextpnr will be installed (this will usually default to `/usr/local`) by using +`-DCMAKE_INSTALL_PREFIX=/install/prefix`. + Notes for developers -------------------- diff --git a/common/command.cc b/common/command.cc index 5070bf9c..206a4d30 100644 --- a/common/command.cc +++ b/common/command.cc @@ -36,11 +36,12 @@ #include "jsonparse.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_files.push_back(stdout); } +CommandHandler::CommandHandler(int argc, char **argv) : argc(argc), argv(argv) { log_streams.clear(); } bool CommandHandler::parseOptions() { @@ -64,14 +65,14 @@ bool CommandHandler::parseOptions() bool CommandHandler::executeBeforeContext() { if (vm.count("help") || argc == 1) { - std::cout << boost::filesystem::basename(argv[0]) + std::cerr << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git sha1 " GIT_COMMIT_HASH_STR ")\n"; - std::cout << options << "\n"; + std::cerr << options << "\n"; return argc != 1; } if (vm.count("version")) { - std::cout << boost::filesystem::basename(argv[0]) + std::cerr << boost::filesystem::basename(argv[0]) << " -- Next Generation Place and Route (git sha1 " GIT_COMMIT_HASH_STR ")\n"; return true; } @@ -84,7 +85,9 @@ 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 displayed"); + 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 @@ -128,7 +131,17 @@ void CommandHandler::setupContext(Context *ctx) } if (vm.count("quiet")) { - log_quiet_warnings = true; + log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING)); + } else { + log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG)); + } + + if (vm.count("log")) { + std::string logfilename = vm["log"].as(); + logfile = std::ofstream(logfilename); + if (!logfile) + log_error("Failed to open log file '%s' for writing.\n", logfilename.c_str()); + log_streams.push_back(std::make_pair(&logfile, LogLevel::LOG)); } if (vm.count("force")) { @@ -144,7 +157,7 @@ void CommandHandler::setupContext(Context *ctx) int r; do { r = rand(); - } while(r == 0); + } while (r == 0); ctx->rngseed(r); } @@ -258,7 +271,7 @@ int CommandHandler::executeMain(std::unique_ptr ctx) deinit_python(); #endif - return 0; + return had_nonfatal_error ? 1 : 0; } void CommandHandler::conflicting_options(const boost::program_options::variables_map &vm, const char *opt1, @@ -270,6 +283,15 @@ void CommandHandler::conflicting_options(const boost::program_options::variables } } +void CommandHandler::printFooter() +{ + int warning_count = get_or_default(message_count_by_level, LogLevel::WARNING, 0), + error_count = get_or_default(message_count_by_level, LogLevel::ERROR, 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 { @@ -288,8 +310,11 @@ int CommandHandler::exec() settings = std::unique_ptr(new Settings(ctx.get())); setupContext(ctx.get()); setupArchContext(ctx.get()); - return executeMain(std::move(ctx)); + int rc = executeMain(std::move(ctx)); + printFooter(); + return rc; } catch (log_execution_error_exception) { + printFooter(); return -1; } } diff --git a/common/command.h b/common/command.h index 12f710f6..d0f1d328 100644 --- a/common/command.h +++ b/common/command.h @@ -54,6 +54,7 @@ class CommandHandler int executeMain(std::unique_ptr ctx); po::options_description getGeneralOptions(); void run_script_hook(const std::string &name); + void printFooter(); protected: po::variables_map vm; @@ -66,6 +67,7 @@ class CommandHandler int argc; char **argv; ProjectHandler project; + std::ofstream logfile; }; NEXTPNR_NAMESPACE_END diff --git a/common/log.cc b/common/log.cc index 6b2d6065..0a75b020 100644 --- a/common/log.cc +++ b/common/log.cc @@ -32,19 +32,15 @@ NEXTPNR_NAMESPACE_BEGIN NPNR_NORETURN void logv_error(const char *format, va_list ap) NPNR_ATTRIBUTE(noreturn); -std::vector log_files; -std::vector log_streams; -FILE *log_errfile = NULL; +std::vector> log_streams; log_write_type log_write_function = nullptr; -bool log_error_stderr = false; -bool log_cmd_error_throw = false; -bool log_quiet_warnings = false; std::string log_last_error; void (*log_error_atexit)() = NULL; -// static bool next_print_log = false; +std::unordered_map message_count_by_level; static int log_newline_count = 0; +bool had_nonfatal_error = false; std::string stringf(const char *fmt, ...) { @@ -88,7 +84,7 @@ std::string vstringf(const char *fmt, va_list ap) return string; } -void logv(const char *format, va_list ap) +void logv(const char *format, va_list ap, LogLevel level = LogLevel::LOG) { // // Trim newlines from the beginning @@ -108,90 +104,51 @@ void logv(const char *format, va_list ap) else log_newline_count = str.size() - nnl_pos - 1; - for (auto f : log_files) - fputs(str.c_str(), f); - for (auto f : log_streams) - *f << str; + if (f.second <= level) + *f.first << str; if (log_write_function) log_write_function(str); } -void logv_info(const char *format, va_list ap) +void log_with_level(LogLevel level, const char *format, ...) +{ + message_count_by_level[level]++; + va_list ap; + va_start(ap, format); + logv(format, ap, level); + va_end(ap); +} + +void logv_prefixed(const char *prefix, const char *format, va_list ap, LogLevel level) { std::string message = vstringf(format, ap); - log_always("Info: %s", message.c_str()); + log_with_level(level, "%s%s", prefix, message.c_str()); log_flush(); } -void logv_warning(const char *format, va_list ap) -{ - std::string message = vstringf(format, ap); - - log_always("Warning: %s", message.c_str()); - log_flush(); -} - -void logv_warning_noprefix(const char *format, va_list ap) -{ - std::string message = vstringf(format, ap); - - log_always("%s", message.c_str()); -} - -void logv_error(const char *format, va_list ap) -{ -#ifdef EMSCRIPTEN - auto backup_log_files = log_files; -#endif - - if (log_errfile != NULL) - log_files.push_back(log_errfile); - - if (log_error_stderr) - for (auto &f : log_files) - if (f == stdout) - f = stderr; - - log_last_error = vstringf(format, ap); - log_always("ERROR: %s", log_last_error.c_str()); - log_flush(); - - if (log_error_atexit) - log_error_atexit(); - -#ifdef EMSCRIPTEN - log_files = backup_log_files; -#endif - throw log_execution_error_exception(); -} - void log_always(const char *format, ...) { va_list ap; va_start(ap, format); - logv(format, ap); + logv(format, ap, LogLevel::ALWAYS); va_end(ap); } void log(const char *format, ...) { - if (log_quiet_warnings) - return; va_list ap; va_start(ap, format); - logv(format, ap); + logv(format, ap, LogLevel::LOG); va_end(ap); } void log_info(const char *format, ...) { - if (log_quiet_warnings) - return; va_list ap; va_start(ap, format); - logv_info(format, ap); + logv_prefixed("Info: ", format, ap, LogLevel::INFO); va_end(ap); } @@ -199,15 +156,7 @@ void log_warning(const char *format, ...) { va_list ap; va_start(ap, format); - logv_warning(format, ap); - va_end(ap); -} - -void log_warning_noprefix(const char *format, ...) -{ - va_list ap; - va_start(ap, format); - logv_warning_noprefix(format, ap); + logv_prefixed("Warning: ", format, ap, LogLevel::WARNING); va_end(ap); } @@ -215,41 +164,35 @@ void log_error(const char *format, ...) { va_list ap; va_start(ap, format); - logv_error(format, ap); -} + logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR); -void log_cmd_error(const char *format, ...) -{ - va_list ap; - va_start(ap, format); + if (log_error_atexit) + log_error_atexit(); - if (log_cmd_error_throw) { - log_last_error = vstringf(format, ap); - log_always("ERROR: %s", log_last_error.c_str()); - log_flush(); - throw log_cmd_error_exception(); - } - - logv_error(format, ap); + throw log_execution_error_exception(); } void log_break() { - if (log_quiet_warnings) - return; if (log_newline_count < 2) - log_always("\n"); + log("\n"); if (log_newline_count < 2) - log_always("\n"); + log("\n"); +} + +void log_nonfatal_error(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR); + va_end(ap); + had_nonfatal_error = true; } void log_flush() { - for (auto f : log_files) - fflush(f); - for (auto f : log_streams) - f->flush(); + f.first->flush(); } NEXTPNR_NAMESPACE_END diff --git a/common/log.h b/common/log.h index b5fddf53..77adbb2f 100644 --- a/common/log.h +++ b/common/log.h @@ -26,8 +26,8 @@ #include #include #include +#include #include - #include "nextpnr.h" NEXTPNR_NAMESPACE_BEGIN @@ -42,14 +42,22 @@ struct log_execution_error_exception { }; -extern std::vector log_files; -extern std::vector log_streams; -extern FILE *log_errfile; +enum class LogLevel +{ + LOG, + INFO, + WARNING, + ERROR, + ALWAYS +}; + +extern std::vector> log_streams; extern log_write_type log_write_function; -extern bool log_quiet_warnings; extern std::string log_last_error; extern void (*log_error_atexit)(); +extern bool had_nonfatal_error; +extern std::unordered_map message_count_by_level; std::string stringf(const char *fmt, ...); std::string vstringf(const char *fmt, va_list ap); @@ -59,10 +67,8 @@ void log(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); void log_always(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); void log_info(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); void log_warning(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); -void log_warning_noprefix(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); NPNR_NORETURN void log_error(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2), noreturn); -NPNR_NORETURN void log_cmd_error(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2), noreturn); - +void log_nonfatal_error(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); void log_break(); void log_flush(); @@ -75,7 +81,6 @@ static inline void log_assert_worker(bool cond, const char *expr, const char *fi NEXTPNR_NAMESPACE_PREFIX log_assert_worker(_assert_expr_, #_assert_expr_, __FILE__, __LINE__) #define log_abort() log_error("Abort in %s:%d.\n", __FILE__, __LINE__) -#define log_ping() log("-- %s:%d %s --\n", __FILE__, __LINE__, __PRETTY_FUNCTION__) NEXTPNR_NAMESPACE_END diff --git a/common/nextpnr.cc b/common/nextpnr.cc index be3bfe14..bb941d3d 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -409,12 +409,16 @@ void Context::check() const void BaseCtx::addClock(IdString net, float freq) { - log_info("constraining clock net '%s' to %.02f MHz\n", net.c_str(this), freq); std::unique_ptr cc(new ClockConstraint()); cc->period = getCtx()->getDelayFromNS(1000 / freq); cc->high = getCtx()->getDelayFromNS(500 / freq); cc->low = getCtx()->getDelayFromNS(500 / freq); - nets.at(net)->clkconstr = std::move(cc); + if (!nets.count(net)) { + log_warning("net '%s' does not exist in design, ignoring clock constraint\n", net.c_str(this)); + } else { + nets.at(net)->clkconstr = std::move(cc); + log_info("constraining clock net '%s' to %.02f MHz\n", net.c_str(this), freq); + } } NEXTPNR_NAMESPACE_END diff --git a/common/placer1.cc b/common/placer1.cc index 0db7ce00..b42ef2ff 100644 --- a/common/placer1.cc +++ b/common/placer1.cc @@ -337,9 +337,10 @@ class SAPlacer } } else { uint64_t score = ctx->rng64(); - if (score <= best_ripup_score) { + CellInfo *bound_cell = ctx->getBoundBelCell(bel); + if (score <= best_ripup_score && bound_cell->belStrength < STRENGTH_STRONG) { best_ripup_score = score; - ripup_target = ctx->getBoundBelCell(bel); + ripup_target = bound_cell; ripup_bel = bel; } } diff --git a/common/pybindings.cc b/common/pybindings.cc index 061dfc47..6cae889d 100644 --- a/common/pybindings.cc +++ b/common/pybindings.cc @@ -68,6 +68,19 @@ void translate_assertfail(const assertion_failure &e) PyErr_SetString(PyExc_AssertionError, e.what()); } +namespace PythonConversion { +template <> struct string_converter +{ + inline PortRef from_str(Context *ctx, std::string name) { NPNR_ASSERT_FALSE("PortRef from_str not implemented"); } + + inline std::string to_str(Context *ctx, const PortRef &pr) + { + return pr.cell->name.str(ctx) + "." + pr.port.str(ctx); + } +}; + +} // namespace PythonConversion + BOOST_PYTHON_MODULE(MODULE_NAME) { register_exception_translator(&translate_assertfail); @@ -120,7 +133,7 @@ BOOST_PYTHON_MODULE(MODULE_NAME) readwrite_wrapper, pass_through>::def_wrap(pi_cls, "type"); - typedef std::vector PortVector; + typedef std::vector PortRefVector; typedef std::unordered_map WireMap; auto ni_cls = class_>("NetInfo", no_init); @@ -128,7 +141,7 @@ BOOST_PYTHON_MODULE(MODULE_NAME) conv_from_str>::def_wrap(ni_cls, "name"); readwrite_wrapper, unwrap_context>::def_wrap(ni_cls, "driver"); - readonly_wrapper>::def_wrap( + readonly_wrapper>::def_wrap( ni_cls, "users"); readonly_wrapper>::def_wrap(ni_cls, "wires"); @@ -141,12 +154,21 @@ BOOST_PYTHON_MODULE(MODULE_NAME) readwrite_wrapper, pass_through>::def_wrap(pr_cls, "budget"); + auto pm_cls = class_>("PipMap", no_init); + readwrite_wrapper, + conv_from_str>::def_wrap(pm_cls, "pip"); + readwrite_wrapper, + pass_through>::def_wrap(pm_cls, "strength"); + def("parse_json", parse_json_shim); def("load_design", load_design_shim, return_value_policy()); WRAP_MAP(AttrMap, pass_through, "AttrMap"); WRAP_MAP(PortMap, wrap_context, "PortMap"); WRAP_MAP(PinMap, conv_to_str, "PinMap"); + WRAP_MAP(WireMap, wrap_context, "WireMap"); + + WRAP_VECTOR(PortRefVector, wrap_context); arch_wrap_python(); } diff --git a/common/pycontainers.h b/common/pycontainers.h index 094706f7..70f69c51 100644 --- a/common/pycontainers.h +++ b/common/pycontainers.h @@ -118,10 +118,67 @@ struct range_wrapper #define WRAP_RANGE(t, conv) \ range_wrapper, conv>().wrap(#t "Range", #t "Iterator") +/* +A wrapper for a vector or similar structure. With support for conversion +*/ + +template , + typename value_conv = PythonConversion::pass_through> +struct vector_wrapper +{ + typedef decltype(std::declval().begin()) iterator_t; + typedef decltype(*(std::declval())) value_t; + typedef typename PythonConversion::ContextualWrapper wrapped_vector; + typedef typename PythonConversion::ContextualWrapper> wrapped_pair; + using return_t = typename value_conv::ret_type; + static wrapped_pair iter(wrapped_vector &range) + { + return wrapped_pair(range.ctx, std::make_pair(range.base.begin(), range.base.end())); + } + + static std::string repr(wrapped_vector &range) + { + PythonConversion::string_converter conv; + bool first = true; + std::stringstream ss; + ss << "["; + for (const auto &item : range.base) { + if (!first) + ss << ", "; + ss << "'" << conv.to_str(range.ctx, item) << "'"; + first = false; + } + ss << "]"; + return ss.str(); + } + + static int len(wrapped_vector &range) { return range.base.size(); } + + static return_t getitem(wrapped_vector &range, int i) + { + return value_conv()(range.ctx, boost::ref(range.base.at(i))); + } + + static void wrap(const char *range_name, const char *iter_name) + { + class_(range_name, no_init) + .def("__iter__", iter) + .def("__repr__", repr) + .def("__len__", len) + .def("__getitem__", getitem); + + iterator_wrapper().wrap(iter_name); + } + + typedef iterator_wrapper iter_wrap; +}; + +#define WRAP_VECTOR(t, conv) vector_wrapper, conv>().wrap(#t, #t "Iterator") + /* Wrapper for a pair, allows accessing either using C++-style members (.first and .second) or as a Python iterable and indexable object - */ +*/ template struct pair_wrapper { typedef std::pair T; diff --git a/common/router1.cc b/common/router1.cc index a3388fa8..cbc0df90 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -812,7 +812,8 @@ bool router1(Context *ctx, const Router1Cfg &cfg) #endif log_info("Checksum: 0x%08x\n", ctx->checksum()); - timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */); + timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */, + true /* warn_on_failure */); ctx->unlock(); return true; @@ -870,7 +871,12 @@ bool Context::checkRoutedDesign() const } auto src_wire = ctx->getNetinfoSourceWire(net_info); - log_assert(src_wire != WireId()); + if (src_wire == WireId()) { + log_assert(net_info->driver.cell == nullptr); + if (ctx->debug) + log(" undriven and unrouted\n"); + continue; + } if (net_info->wires.count(src_wire) == 0) { if (ctx->debug) diff --git a/common/timing.cc b/common/timing.cc index 80be554c..88ab14c2 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -485,11 +485,11 @@ void assign_budget(Context *ctx, bool quiet) for (auto &user : net.second->users) { // Post-update check if (!ctx->auto_freq && user.budget < 0) - log_warning("port %s.%s, connected to net '%s', has negative " - "timing budget of %fns\n", - user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx), - ctx->getDelayNS(user.budget)); - else if (ctx->verbose) + log_info("port %s.%s, connected to net '%s', has negative " + "timing budget of %fns\n", + user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx), + ctx->getDelayNS(user.budget)); + else if (ctx->debug) log_info("port %s.%s, connected to net '%s', has " "timing budget of %fns\n", user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx), @@ -513,7 +513,7 @@ void assign_budget(Context *ctx, bool quiet) log_info("Checksum: 0x%08x\n", ctx->checksum()); } -void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path) +void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path, bool warn_on_failure) { auto format_event = [ctx](const ClockEvent &e, int field_width = 0) { std::string value; @@ -593,7 +593,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p if (print_path) { auto print_path_report = [ctx](ClockPair &clocks, PortRefVector &crit_path) { - delay_t total = 0; + delay_t total = 0, logic_total = 0, route_total = 0; auto &front = crit_path.front(); auto &front_port = front->cell->ports.at(front->port); auto &front_driver = front_port.net->driver; @@ -608,6 +608,9 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p if (clknet != nullptr && clknet->name == clocks.start.clock && clockInfo.edge == clocks.start.edge) { last_port = clockInfo.clock_port; + total += clockInfo.clockToQ.maxDelay(); + logic_total += clockInfo.clockToQ.maxDelay(); + break; } } } @@ -627,10 +630,12 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay); } total += comb_delay.maxDelay(); + logic_total += comb_delay.maxDelay(); log_info("%4.1f %4.1f Source %s.%s\n", ctx->getDelayNS(comb_delay.maxDelay()), ctx->getDelayNS(total), driver_cell->name.c_str(ctx), driver.port.c_str(ctx)); auto net_delay = ctx->getNetinfoRouteDelay(net, *sink); total += net_delay; + route_total += net_delay; auto driver_loc = ctx->getBelLocation(driver_cell->bel); auto sink_loc = ctx->getBelLocation(sink_cell->bel); log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n", ctx->getDelayNS(net_delay), @@ -658,6 +663,17 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p } last_port = sink->port; } + int clockCount = 0; + auto sinkClass = ctx->getPortTimingClass(crit_path.back()->cell, crit_path.back()->port, clockCount); + if (sinkClass == TMG_REGISTER_INPUT && clockCount > 0) { + auto sinkClockInfo = ctx->getPortClockingInfo(crit_path.back()->cell, crit_path.back()->port, 0); + delay_t setup = sinkClockInfo.setup.maxDelay(); + total += setup; + logic_total += setup; + log_info("%4.1f %4.1f Setup %s.%s\n", ctx->getDelayNS(setup), ctx->getDelayNS(total), + crit_path.back()->cell->name.c_str(ctx), crit_path.back()->port.c_str(ctx)); + } + log_info("%.1f ns logic, %.1f ns routing\n", ctx->getDelayNS(logic_total), ctx->getDelayNS(route_total)); }; for (auto &clock : clock_reports) { @@ -689,15 +705,17 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p for (auto &clock : clock_reports) { const auto &clock_name = clock.first.str(ctx); const int width = max_width - clock_name.size(); - if (ctx->nets.at(clock.first)->clkconstr) { - float target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay()); + float target = ctx->target_freq / 1e6; + if (ctx->nets.at(clock.first)->clkconstr) + target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay()); + + bool passed = target < clock_fmax[clock.first]; + if (!warn_on_failure || passed) log_info("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "", - clock_name.c_str(), clock_fmax[clock.first], - (target < clock_fmax[clock.first]) ? "PASS" : "FAIL", target); - } else { - log_info("Max frequency for clock %*s'%s': %.02f MHz\n", width, "", clock_name.c_str(), - clock_fmax[clock.first]); - } + clock_name.c_str(), clock_fmax[clock.first], passed ? "PASS" : "FAIL", target); + else + log_nonfatal_error("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "", + clock_name.c_str(), clock_fmax[clock.first], passed ? "PASS" : "FAIL", target); } for (auto &eclock : empty_clocks) { if (eclock != ctx->id("$async$")) diff --git a/common/timing.h b/common/timing.h index 1fd76310..42f928dc 100644 --- a/common/timing.h +++ b/common/timing.h @@ -29,7 +29,8 @@ void assign_budget(Context *ctx, bool quiet = false); // Perform timing analysis and print out the fmax, and optionally the // critical path -void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false); +void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false, + bool warn_on_failure = false); NEXTPNR_NAMESPACE_END diff --git a/docs/constraints.md b/docs/constraints.md index 263df7b6..c12204da 100644 --- a/docs/constraints.md +++ b/docs/constraints.md @@ -2,7 +2,7 @@ There are three types of constraints available for end users of nextpnr. -## Architecture-specific IO Cconstraints +## Architecture-specific IO Constraints Architectures may provide support for their native (or any other) IO constraint format. The iCE40 architecture supports PCF constraints thus: diff --git a/ecp5/arch.cc b/ecp5/arch.cc index afea8d4a..22b350c7 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -96,7 +96,7 @@ Arch::Arch(ArchArgs args) : args(args) break; } } - + speed_grade = &(chip_info->speed_grades[args.speed]); if (!package_info) log_error("Unsupported package '%s' for '%s'.\n", args.package.c_str(), getChipName().c_str()); @@ -400,7 +400,7 @@ BelId Arch::getBelByLocation(Loc loc) const delay_t Arch::estimateDelay(WireId src, WireId dst) const { - return 170 * (abs(src.location.x - dst.location.x) + abs(src.location.y - dst.location.y)); + return (240 - 20 * args.speed) * (abs(src.location.x - dst.location.x) + abs(src.location.y - dst.location.y)); } delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const @@ -409,7 +409,7 @@ delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const auto driver_loc = getBelLocation(driver.cell->bel); auto sink_loc = getBelLocation(sink.cell->bel); - return 170 * (abs(driver_loc.x - sink_loc.x) + abs(driver_loc.y - sink_loc.y)); + return (240 - 20 * args.speed) * (abs(driver_loc.x - sink_loc.x) + abs(driver_loc.y - sink_loc.y)); } bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; } @@ -422,7 +422,34 @@ bool Arch::route() { route_ecp5_globals(getCtx()); assign_budget(getCtx(), true); - return router1(getCtx(), Router1Cfg(getCtx())); + + bool result = router1(getCtx(), Router1Cfg(getCtx())); +#if 0 + std::vector> fanout_vector; + std::copy(wire_fanout.begin(), wire_fanout.end(), std::back_inserter(fanout_vector)); + std::sort(fanout_vector.begin(), fanout_vector.end(), [](const std::pair &a, const std::pair &b) { + return a.second > b.second; + }); + for (size_t i = 0; i < std::min(size_t(20), fanout_vector.size()); i++) + log_info(" fanout %s = %d\n", getWireName(fanout_vector[i].first).c_str(this), fanout_vector[i].second); + log_break(); + PipId slowest_pip; + delay_t slowest_pipdelay = 0; + for (auto pip : pip_to_net) { + if (pip.second) { + delay_t dly = getPipDelay(pip.first).maxDelay(); + if (dly > slowest_pipdelay) { + slowest_pip = pip.first; + slowest_pipdelay = dly; + } + } + } + log_info(" slowest pip %s = %.02f ns\n", getPipName(slowest_pip).c_str(this), getDelayNS(slowest_pipdelay)); + log_info(" fanout %d\n", wire_fanout[getPipSrcWire(slowest_pip)]); + log_info(" base %d adder %d\n", speed_grade->pip_classes[locInfo(slowest_pip)->pip_data[slowest_pip.index].timing_class].max_base_delay, + speed_grade->pip_classes[locInfo(slowest_pip)->pip_data[slowest_pip.index].timing_class].max_fanout_adder); +#endif + return result; } // ----------------------------------------------------------------------- @@ -482,94 +509,75 @@ DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; }; // ----------------------------------------------------------------------- +bool Arch::getDelayFromTimingDatabase(IdString tctype, IdString from, IdString to, DelayInfo &delay) const +{ + for (int i = 0; i < speed_grade->num_cell_timings; i++) { + const auto &tc = speed_grade->cell_timings[i]; + if (tc.cell_type == tctype.index) { + for (int j = 0; j < tc.num_prop_delays; j++) { + const auto &dly = tc.prop_delays[j]; + if (dly.from_port == from.index && dly.to_port == to.index) { + delay.max_delay = dly.max_delay; + delay.min_delay = dly.min_delay; + return true; + } + } + return false; + } + } + NPNR_ASSERT_FALSE("failed to find timing cell in db"); +} + +void Arch::getSetupHoldFromTimingDatabase(IdString tctype, IdString clock, IdString port, DelayInfo &setup, + DelayInfo &hold) const +{ + for (int i = 0; i < speed_grade->num_cell_timings; i++) { + const auto &tc = speed_grade->cell_timings[i]; + if (tc.cell_type == tctype.index) { + for (int j = 0; j < tc.num_setup_holds; j++) { + const auto &sh = tc.setup_holds[j]; + if (sh.clock_port == clock.index && sh.sig_port == port.index) { + setup.max_delay = sh.max_setup; + setup.min_delay = sh.min_setup; + hold.max_delay = sh.max_hold; + hold.min_delay = sh.min_hold; + return; + } + } + } + } + NPNR_ASSERT_FALSE("failed to find timing cell in db"); +} + bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const { + // Data for -8 grade if (cell->type == id_TRELLIS_SLICE) { bool has_carry = str_or_default(cell->params, id("MODE"), "LOGIC") == "CCU2"; - if (fromPort == id_A0 || fromPort == id_B0 || fromPort == id_C0 || fromPort == id_D0) { - if (toPort == id_F0) { - delay.delay = 180; - return true; - } else if (has_carry && toPort == id_F1) { - delay.delay = 500; - return true; - } else if (has_carry && toPort == id_FCO) { - delay.delay = 355; - return true; - } else if (toPort == id_OFX0) { - delay.delay = 306; - return true; - } + if (fromPort == id_A0 || fromPort == id_B0 || fromPort == id_C0 || fromPort == id_D0 || fromPort == id_A1 || + fromPort == id_B1 || fromPort == id_C1 || fromPort == id_D1 || fromPort == id_M0 || fromPort == id_M1 || + fromPort == id_FXA || fromPort == id_FXB || fromPort == id_FCI) { + return getDelayFromTimingDatabase(has_carry ? id_SCCU2C : id_SLOGICB, fromPort, toPort, delay); } - if (fromPort == id_A1 || fromPort == id_B1 || fromPort == id_C1 || fromPort == id_D1) { - if (toPort == id_F1) { - delay.delay = 180; - return true; - } else if (has_carry && toPort == id_FCO) { - delay.delay = 355; - return true; - } else if (toPort == id_OFX0) { - delay.delay = 306; - return true; - } - } - - if (has_carry && fromPort == id_FCI) { - if (toPort == id_F0) { - delay.delay = 328; - return true; - } else if (toPort == id_F1) { - delay.delay = 349; - return true; - } else if (toPort == id_FCO) { - delay.delay = 56; - return true; - } - } - - if (fromPort == id_CLK && (toPort == id_Q0 || toPort == id_Q1)) { - delay.delay = 395; - return true; - } - - if (fromPort == id_M0 && toPort == id_OFX0) { - delay.delay = 193; - return true; - } -#if 0 // FIXME - if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) { - delay.delay = 717; - return true; - } -#endif if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) || (fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) || (fromPort == id_C0 && toPort == id_WADO2) || (fromPort == id_C1 && toPort == id_WDO0) || (fromPort == id_D0 && toPort == id_WADO0) || (fromPort == id_D1 && toPort == id_WDO2)) { - delay.delay = 0; + delay.min_delay = 0; + delay.max_delay = 0; return true; } return false; } else if (cell->type == id_DCCA) { if (fromPort == id_CLKI && toPort == id_CLKO) { - delay.delay = 0; + delay.min_delay = 0; + delay.max_delay = 0; return true; } return false; } else if (cell->type == id_DP16KD) { - if (fromPort == id_CLKA) { - if (toPort.str(this).substr(0, 3) == "DOA") { - delay.delay = 4260; - return true; - } - } else if (fromPort == id_CLKB) { - if (toPort.str(this).substr(0, 3) == "DOB") { - delay.delay = 4280; - return true; - } - } return false; } else { return false; @@ -669,9 +677,9 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const { TimingClockingInfo info; - info.setup.delay = 0; - info.hold.delay = 0; - info.clockToQ.delay = 0; + info.setup = getDelayFromNS(0); + info.hold = getDelayFromNS(0); + info.clockToQ = getDelayFromNS(0); if (cell->type == id_TRELLIS_SLICE) { int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0); @@ -679,18 +687,18 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port port == id_WAD3 || port == id_WRE) { info.edge = RISING_EDGE; info.clock_port = id_WCK; - info.setup.delay = 100; - info.hold.delay = 0; + getSetupHoldFromTimingDatabase(id_SDPRAME, id_WCK, port, info.setup, info.hold); } else if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) || (sd1 == 1 && port == id_M1)) { info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE; info.clock_port = id_CLK; - info.setup.delay = 100; - info.hold.delay = 0; + getSetupHoldFromTimingDatabase(id_SLOGICB, id_CLK, port, info.setup, info.hold); + } else { info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE; info.clock_port = id_CLK; - info.clockToQ.delay = 395; + bool is_path = getDelayFromTimingDatabase(id_SLOGICB, id_CLK, port, info.clockToQ); + NPNR_ASSERT(is_path); } } else if (cell->type == id_DP16KD) { std::string port_name = port.str(this); @@ -711,10 +719,12 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port ? FALLING_EDGE : RISING_EDGE; if (cell->ports.at(port).type == PORT_OUT) { - info.clockToQ.delay = 4280; + bool is_path = getDelayFromTimingDatabase(id_DP16KD_REGMODE_A_NOREG_REGMODE_B_NOREG, info.clock_port, port, + info.clockToQ); + NPNR_ASSERT(is_path); } else { - info.setup.delay = 100; - info.hold.delay = 0; + getSetupHoldFromTimingDatabase(id_DP16KD_REGMODE_A_NOREG_REGMODE_B_NOREG, info.clock_port, port, info.setup, + info.hold); } } else if (cell->type == id_DCUA) { std::string prefix = port.str(this).substr(0, 9); @@ -728,10 +738,10 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port else if (prefix == "CH1_FF_RX") info.clock_port = id_CH1_FF_RXI_CLK; if (cell->ports.at(port).type == PORT_OUT) { - info.clockToQ.delay = 660; + info.clockToQ = getDelayFromNS(0.7); } else { - info.setup.delay = 1000; - info.hold.delay = 0; + info.setup = getDelayFromNS(1); + info.hold = getDelayFromNS(0); } } return info; diff --git a/ecp5/arch.h b/ecp5/arch.h index aa3c5348..a68673f4 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -71,7 +71,7 @@ NPNR_PACKED_STRUCT(struct BelPortPOD { NPNR_PACKED_STRUCT(struct PipInfoPOD { LocationPOD rel_src_loc, rel_dst_loc; int32_t src_idx, dst_idx; - int32_t delay; + int32_t timing_class; int16_t tile_type; int8_t pip_type; int8_t padding_0; @@ -151,6 +151,44 @@ NPNR_PACKED_STRUCT(struct GlobalInfoPOD { int16_t spine_col; }); +NPNR_PACKED_STRUCT(struct CellPropDelayPOD { + int32_t from_port; + int32_t to_port; + int32_t min_delay; + int32_t max_delay; +}); + +NPNR_PACKED_STRUCT(struct CellSetupHoldPOD { + int32_t sig_port; + int32_t clock_port; + int32_t min_setup; + int32_t max_setup; + int32_t min_hold; + int32_t max_hold; +}); + +NPNR_PACKED_STRUCT(struct CellTimingPOD { + int32_t cell_type; + int32_t num_prop_delays; + int32_t num_setup_holds; + RelPtr prop_delays; + RelPtr setup_holds; +}); + +NPNR_PACKED_STRUCT(struct PipDelayPOD { + int32_t min_base_delay; + int32_t max_base_delay; + int32_t min_fanout_adder; + int32_t max_fanout_adder; +}); + +NPNR_PACKED_STRUCT(struct SpeedGradePOD { + int32_t num_cell_timings; + int32_t num_pip_classes; + RelPtr cell_timings; + RelPtr pip_classes; +}); + NPNR_PACKED_STRUCT(struct ChipInfoPOD { int32_t width, height; int32_t num_tiles; @@ -163,6 +201,7 @@ NPNR_PACKED_STRUCT(struct ChipInfoPOD { RelPtr package_info; RelPtr pio_info; RelPtr tile_info; + RelPtr speed_grades; }); #if defined(_MSC_VER) @@ -400,13 +439,20 @@ struct ArchArgs LFE5UM5G_85F, } type = NONE; std::string package; - int speed = 6; + enum SpeedGrade + { + SPEED_6 = 0, + SPEED_7, + SPEED_8, + SPEED_8_5G, + } speed = SPEED_6; }; struct Arch : BaseCtx { const ChipInfoPOD *chip_info; const PackageInfoPOD *package_info; + const SpeedGradePOD *speed_grade; mutable std::unordered_map bel_by_name; mutable std::unordered_map wire_by_name; @@ -415,6 +461,7 @@ struct Arch : BaseCtx std::vector bel_to_cell; std::unordered_map wire_to_net; std::unordered_map pip_to_net; + std::unordered_map wire_fanout; ArchArgs args; Arch(ArchArgs args); @@ -597,6 +644,7 @@ struct Arch : BaseCtx auto pip = it->second.pip; if (pip != PipId()) { + wire_fanout[getPipSrcWire(pip)]--; pip_to_net[pip] = nullptr; } @@ -633,7 +681,8 @@ struct Arch : BaseCtx DelayInfo getWireDelay(WireId wire) const { DelayInfo delay; - delay.delay = 0; + delay.min_delay = 0; + delay.max_delay = 0; return delay; } @@ -686,6 +735,7 @@ struct Arch : BaseCtx NPNR_ASSERT(pip_to_net[pip] == nullptr); pip_to_net[pip] = net; + wire_fanout[getPipSrcWire(pip)]++; WireId dst; dst.index = locInfo(pip)->pip_data[pip.index].dst_idx; @@ -700,6 +750,7 @@ struct Arch : BaseCtx { NPNR_ASSERT(pip != PipId()); NPNR_ASSERT(pip_to_net[pip] != nullptr); + wire_fanout[getPipSrcWire(pip)]--; WireId dst; dst.index = locInfo(pip)->pip_data[pip.index].dst_idx; @@ -772,7 +823,17 @@ struct Arch : BaseCtx { DelayInfo delay; NPNR_ASSERT(pip != PipId()); - delay.delay = locInfo(pip)->pip_data[pip.index].delay; + int fanout = 0; + auto fnd_fanout = wire_fanout.find(getPipSrcWire(pip)); + if (fnd_fanout != wire_fanout.end()) + fanout = fnd_fanout->second; + NPNR_ASSERT(locInfo(pip)->pip_data[pip.index].timing_class < speed_grade->num_pip_classes); + delay.min_delay = + speed_grade->pip_classes[locInfo(pip)->pip_data[pip.index].timing_class].min_base_delay + + fanout * speed_grade->pip_classes[locInfo(pip)->pip_data[pip.index].timing_class].min_fanout_adder; + delay.max_delay = + speed_grade->pip_classes[locInfo(pip)->pip_data[pip.index].timing_class].max_base_delay + + fanout * speed_grade->pip_classes[locInfo(pip)->pip_data[pip.index].timing_class].max_fanout_adder; return delay; } @@ -862,7 +923,8 @@ struct Arch : BaseCtx DelayInfo getDelayFromNS(float ns) const { DelayInfo del; - del.delay = delay_t(ns * 1000); + del.min_delay = delay_t(ns * 1000); + del.max_delay = delay_t(ns * 1000); return del; } uint32_t getDelayChecksum(delay_t v) const { return v; } @@ -895,6 +957,10 @@ struct Arch : BaseCtx // Return true if a port is a net bool isGlobalNet(const NetInfo *net) const; + bool getDelayFromTimingDatabase(IdString tctype, IdString from, IdString to, DelayInfo &delay) const; + void getSetupHoldFromTimingDatabase(IdString tctype, IdString clock, IdString port, DelayInfo &setup, + DelayInfo &hold) const; + // ------------------------------------------------- // Placement validity checks bool isValidBelForCell(CellInfo *cell, BelId bel) const; diff --git a/ecp5/arch_place.cc b/ecp5/arch_place.cc index 41f87cb8..ff70bb5a 100644 --- a/ecp5/arch_place.cc +++ b/ecp5/arch_place.cc @@ -75,6 +75,8 @@ bool Arch::isBelLocationValid(BelId bel) const bel_cells.push_back(cell_other); } } + if (getBoundBelCell(bel) != nullptr && getBoundBelCell(bel)->sliceInfo.has_l6mux && ((bel_loc.z % 2) == 1)) + return false; return slicesCompatible(bel_cells); } else { CellInfo *cell = getBoundBelCell(bel); @@ -92,6 +94,10 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const std::vector bel_cells; Loc bel_loc = getBelLocation(bel); + + if (cell->sliceInfo.has_l6mux && ((bel_loc.z % 2) == 1)) + return false; + for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) { CellInfo *cell_other = getBoundBelCell(bel_other); if (cell_other != nullptr && bel_other != bel) { diff --git a/ecp5/arch_pybindings.h b/ecp5/arch_pybindings.h index f18b5ff1..9bd7bcdf 100644 --- a/ecp5/arch_pybindings.h +++ b/ecp5/arch_pybindings.h @@ -44,14 +44,36 @@ template <> struct string_converter { WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); } - std::string to_str(Context *ctx, WireId id) { return ctx->getWireName(id).str(ctx); } + std::string to_str(Context *ctx, WireId id) + { + if (id == WireId()) + throw bad_wrap(); + return ctx->getWireName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); } + + std::string to_str(Context *ctx, WireId id) + { + if (id == WireId()) + throw bad_wrap(); + return ctx->getWireName(id).str(ctx); + } }; template <> struct string_converter { PipId from_str(Context *ctx, std::string name) { return ctx->getPipByName(ctx->id(name)); } - std::string to_str(Context *ctx, PipId id) { return ctx->getPipName(id).str(ctx); } + std::string to_str(Context *ctx, PipId id) + { + if (id == PipId()) + throw bad_wrap(); + return ctx->getPipName(id).str(ctx); + } }; } // namespace PythonConversion diff --git a/ecp5/archdefs.h b/ecp5/archdefs.h index b2c23134..bfc5769b 100644 --- a/ecp5/archdefs.h +++ b/ecp5/archdefs.h @@ -30,21 +30,22 @@ typedef int delay_t; struct DelayInfo { - delay_t delay = 0; + delay_t min_delay = 0, max_delay = 0; - delay_t minRaiseDelay() const { return delay; } - delay_t maxRaiseDelay() const { return delay; } + delay_t minRaiseDelay() const { return min_delay; } + delay_t maxRaiseDelay() const { return max_delay; } - delay_t minFallDelay() const { return delay; } - delay_t maxFallDelay() const { return delay; } + delay_t minFallDelay() const { return min_delay; } + delay_t maxFallDelay() const { return max_delay; } - delay_t minDelay() const { return delay; } - delay_t maxDelay() const { return delay; } + delay_t minDelay() const { return min_delay; } + delay_t maxDelay() const { return max_delay; } DelayInfo operator+(const DelayInfo &other) const { DelayInfo ret; - ret.delay = this->delay + other.delay; + ret.min_delay = this->min_delay + other.min_delay; + ret.max_delay = this->max_delay + other.max_delay; return ret; } }; @@ -157,6 +158,7 @@ struct ArchCellInfo struct { bool using_dff; + bool has_l6mux; IdString clk_sig, lsr_sig, clkmux, lsrmux, srmode; } sliceInfo; }; diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 4de2a0a6..df16946d 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -407,8 +407,8 @@ std::vector get_pll_tiles(Context *ctx, BelId bel) tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x, "PLL0_LR")); tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x - 1, pll1_lr)); } else if (name == "EHXPLL_UR") { - tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x - 1, "PLL0_UR")); - tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x - 1, "PLL1_UR")); + tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x + 1, "PLL0_UR")); + tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x + 1, "PLL1_UR")); } else { NPNR_ASSERT_FALSE_STR("bad PLL loc " + name); } diff --git a/ecp5/cells.h b/ecp5/cells.h index 9c2ff3cf..dcef99e3 100644 --- a/ecp5/cells.h +++ b/ecp5/cells.h @@ -36,7 +36,7 @@ inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type inline bool is_carry(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("CCU2C"); } -inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_LC"); } +inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_SLICE"); } inline bool is_trellis_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_IO"); } diff --git a/ecp5/constids.inc b/ecp5/constids.inc index 11ecc240..250bc3fc 100644 --- a/ecp5/constids.inc +++ b/ecp5/constids.inc @@ -811,7 +811,6 @@ X(INTLOCK) X(REFCLK) X(CLKINTFB) - X(EXTREFB) X(REFCLKP) X(REFCLKN) @@ -1116,4 +1115,30 @@ X(SEL2) X(SEL1) X(SEL0) X(CDIV1) -X(CDIVX) \ No newline at end of file +X(CDIVX) + +X(DP16KD_REGMODE_A_NOREG_REGMODE_B_NOREG) +X(DP16KD_REGMODE_A_NOREG_REGMODE_B_OUTREG) +X(DP16KD_REGMODE_A_OUTREG_REGMODE_B_NOREG) +X(DP16KD_REGMODE_A_OUTREG_REGMODE_B_OUTREG) +X(DP16KD_WRITEMODE_A_NORMAL_WRITEMODE_B_NORMAL) +X(DP16KD_WRITEMODE_A_NORMAL_WRITEMODE_B_READBEFOREWRITE) +X(DP16KD_WRITEMODE_A_NORMAL_WRITEMODE_B_WRITETHROUGH) +X(PIO_IOTYPE_LVCMOS12) +X(PIO_IOTYPE_LVCMOS15) +X(PIO_IOTYPE_LVCMOS18) +X(PIO_IOTYPE_LVCMOS25) +X(PIO_IOTYPE_LVCMOS33) +X(PIO_IOTYPE_LVDS) +X(PIO_IOTYPE_SSTL15_I) +X(PIO_IOTYPE_SSTL15_II) +X(PIO_IOTYPE_SSTL18_I) +X(PIO_IOTYPE_SSTL18_II) +X(SCCU2C) +X(SDPRAME) +X(SLOGICB) +X(SRAMWB) +X(PAD) +X(PADDI) +X(PADDO) +X(PADDT) diff --git a/ecp5/family.cmake b/ecp5/family.cmake index ebe654af..1aae2bea 100644 --- a/ecp5/family.cmake +++ b/ecp5/family.cmake @@ -18,11 +18,13 @@ file(MAKE_DIRECTORY ecp5/chipdbs/) add_library(ecp5_chipdb OBJECT ecp5/chipdbs/) target_compile_definitions(ecp5_chipdb PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family}) target_include_directories(ecp5_chipdb PRIVATE ${family}/) + if (WIN32) -set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=\"${TRELLIS_ROOT}/libtrellis\;${TRELLIS_ROOT}/util/common\"") +set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=\"${TRELLIS_ROOT}/libtrellis\;${TRELLIS_ROOT}/util/common\;${TRELLIS_ROOT}/timing/util\"") else() -set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=${TRELLIS_ROOT}/libtrellis:${TRELLIS_ROOT}/util/common") +set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=${TRELLIS_ROOT}/libtrellis:${TRELLIS_ROOT}/util/common:${TRELLIS_ROOT}/timing/util") endif() + if (MSVC) target_sources(ecp5_chipdb PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/resource/embed.cc) set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ecp5/resources/chipdb.rc PROPERTIES LANGUAGE RC) diff --git a/ecp5/main.cc b/ecp5/main.cc index cc004df3..12afb09d 100644 --- a/ecp5/main.cc +++ b/ecp5/main.cc @@ -59,6 +59,8 @@ po::options_description ECP5CommandHandler::getArchOptions() specific.add_options()("um5g-85k", "set device type to LFE5UM5G-85F"); specific.add_options()("package", po::value(), "select device package (defaults to CABGA381)"); + specific.add_options()("speed", po::value(), "select device speedgrade (6, 7 or 8)"); + specific.add_options()("basecfg", po::value(), "base chip configuration in Trellis text format"); specific.add_options()("textcfg", po::value(), "textual configuration in Trellis format to write"); @@ -111,7 +113,31 @@ std::unique_ptr ECP5CommandHandler::createContext() chipArgs.package = vm["package"].as(); else chipArgs.package = "CABGA381"; - chipArgs.speed = 6; + if (chipArgs.type == ArchArgs::LFE5UM5G_25F || chipArgs.type == ArchArgs::LFE5UM5G_45F || + chipArgs.type == ArchArgs::LFE5UM5G_85F) { + if (vm.count("speed") && vm["speed"].as() != 8) + log_error("Only speed grade 8 is available for 5G parts\n"); + chipArgs.speed = ArchArgs::SPEED_8_5G; + } else { + if (vm.count("speed")) { + int speed = vm["speed"].as(); + switch (speed) { + case 6: + chipArgs.speed = ArchArgs::SPEED_6; + break; + case 7: + chipArgs.speed = ArchArgs::SPEED_7; + break; + case 8: + chipArgs.speed = ArchArgs::SPEED_8; + break; + default: + log_error("Unsupported speed grade '%d'\n", speed); + } + } else { + chipArgs.speed = ArchArgs::SPEED_6; + } + } return std::unique_ptr(new Context(chipArgs)); } diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 2d2f7578..78bf7a87 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -357,9 +357,9 @@ class Ecp5Packer } // Pass to pack LUT5s into a newly created slice - void pack_lut5s() + void pack_lut5xs() { - log_info("Packing LUT5s...\n"); + log_info("Packing LUT5-7s...\n"); for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (is_pfumx(ctx, ci)) { @@ -377,6 +377,8 @@ class Ecp5Packer log_error("PFUMX '%s' has BLUT driven by cell other than a LUT\n", ci->name.c_str(ctx)); if (lut1 == nullptr) log_error("PFUMX '%s' has ALUT driven by cell other than a LUT\n", ci->name.c_str(ctx)); + if (ctx->verbose) + log_info(" mux '%s' forms part of a LUT5\n", cell.first.c_str(ctx)); replace_port(lut0, ctx->id("A"), packed.get(), ctx->id("A0")); replace_port(lut0, ctx->id("B"), packed.get(), ctx->id("B0")); replace_port(lut0, ctx->id("C"), packed.get(), ctx->id("C0")); @@ -412,8 +414,157 @@ class Ecp5Packer } } flush_cells(); - } + // Pack LUT6s + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_l6mux(ctx, ci)) { + NetInfo *ofx0_0 = ci->ports.at(ctx->id("D0")).net; + if (ofx0_0 == nullptr) + log_error("L6MUX21 '%s' has disconnected port 'D0'\n", ci->name.c_str(ctx)); + NetInfo *ofx0_1 = ci->ports.at(ctx->id("D1")).net; + if (ofx0_1 == nullptr) + log_error("L6MUX21 '%s' has disconnected port 'D1'\n", ci->name.c_str(ctx)); + CellInfo *slice0 = net_driven_by(ctx, ofx0_0, is_lc, ctx->id("OFX0")); + CellInfo *slice1 = net_driven_by(ctx, ofx0_1, is_lc, ctx->id("OFX0")); + if (slice0 == nullptr) { + if (!net_driven_by(ctx, ofx0_0, is_l6mux, ctx->id("Z")) && + !net_driven_by(ctx, ofx0_0, is_lc, ctx->id("OFX1"))) + log_error("L6MUX21 '%s' has D0 driven by cell other than a SLICE OFX0 but not a LUT7 mux " + "('%s.%s')\n", + ci->name.c_str(ctx), ofx0_0->driver.cell->name.c_str(ctx), + ofx0_0->driver.port.c_str(ctx)); + continue; + } + if (slice1 == nullptr) { + if (!net_driven_by(ctx, ofx0_1, is_l6mux, ctx->id("Z")) && + !net_driven_by(ctx, ofx0_1, is_lc, ctx->id("OFX1"))) + log_error("L6MUX21 '%s' has D1 driven by cell other than a SLICE OFX0 but not a LUT7 mux " + "('%s.%s')\n", + ci->name.c_str(ctx), ofx0_0->driver.cell->name.c_str(ctx), + ofx0_0->driver.port.c_str(ctx)); + continue; + } + if (ctx->verbose) + log_info(" mux '%s' forms part of a LUT6\n", cell.first.c_str(ctx)); + replace_port(ci, ctx->id("D0"), slice1, id_FXA); + replace_port(ci, ctx->id("D1"), slice1, id_FXB); + replace_port(ci, ctx->id("SD"), slice1, id_M1); + replace_port(ci, ctx->id("Z"), slice1, id_OFX1); + slice0->constr_z = 1; + slice0->constr_x = 0; + slice0->constr_y = 0; + slice0->constr_parent = slice1; + slice1->constr_z = 0; + slice1->constr_abs_z = true; + slice1->constr_children.push_back(slice0); + if (lutffPairs.find(ci->name) != lutffPairs.end()) { + CellInfo *ff = ctx->cells.at(lutffPairs[ci->name]).get(); + ff_to_slice(ctx, ff, slice1, 1, true); + packed_cells.insert(ff->name); + sliceUsage[slice1->name].ff1_used = true; + lutffPairs.erase(ci->name); + fflutPairs.erase(ff->name); + } + + packed_cells.insert(ci->name); + } + } + flush_cells(); + // Pack LUT7s + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_l6mux(ctx, ci)) { + NetInfo *ofx1_0 = ci->ports.at(ctx->id("D0")).net; + if (ofx1_0 == nullptr) + log_error("L6MUX21 '%s' has disconnected port 'D0'\n", ci->name.c_str(ctx)); + NetInfo *ofx1_1 = ci->ports.at(ctx->id("D1")).net; + if (ofx1_1 == nullptr) + log_error("L6MUX21 '%s' has disconnected port 'D1'\n", ci->name.c_str(ctx)); + CellInfo *slice1 = net_driven_by(ctx, ofx1_0, is_lc, ctx->id("OFX1")); + CellInfo *slice3 = net_driven_by(ctx, ofx1_1, is_lc, ctx->id("OFX1")); + if (slice1 == nullptr) + log_error("L6MUX21 '%s' has D0 driven by cell other than a SLICE OFX ('%s.%s')\n", + ci->name.c_str(ctx), ofx1_0->driver.cell->name.c_str(ctx), + ofx1_0->driver.port.c_str(ctx)); + if (slice3 == nullptr) + log_error("L6MUX21 '%s' has D1 driven by cell other than a SLICE OFX ('%s.%s')\n", + ci->name.c_str(ctx), ofx1_1->driver.cell->name.c_str(ctx), + ofx1_1->driver.port.c_str(ctx)); + + NetInfo *fxa_0 = slice1->ports.at(id_FXA).net; + if (fxa_0 == nullptr) + log_error("SLICE '%s' has disconnected port 'FXA'\n", slice1->name.c_str(ctx)); + NetInfo *fxa_1 = slice3->ports.at(id_FXA).net; + if (fxa_1 == nullptr) + log_error("SLICE '%s' has disconnected port 'FXA'\n", slice3->name.c_str(ctx)); + + CellInfo *slice0 = net_driven_by(ctx, fxa_0, is_lc, ctx->id("OFX0")); + CellInfo *slice2 = net_driven_by(ctx, fxa_1, is_lc, ctx->id("OFX0")); + if (slice0 == nullptr) + log_error("SLICE '%s' has FXA driven by cell other than a SLICE OFX0 ('%s.%s')\n", + slice1->name.c_str(ctx), fxa_0->driver.cell->name.c_str(ctx), + fxa_0->driver.port.c_str(ctx)); + if (slice2 == nullptr) + log_error("SLICE '%s' has FXA driven by cell other than a SLICE OFX0 ('%s.%s')\n", + slice3->name.c_str(ctx), fxa_1->driver.cell->name.c_str(ctx), + fxa_1->driver.port.c_str(ctx)); + + replace_port(ci, ctx->id("D0"), slice2, id_FXA); + replace_port(ci, ctx->id("D1"), slice2, id_FXB); + replace_port(ci, ctx->id("SD"), slice2, id_M1); + replace_port(ci, ctx->id("Z"), slice2, id_OFX1); + + for (auto slice : {slice0, slice1, slice2, slice3}) { + slice->constr_children.clear(); + slice->constr_abs_z = false; + slice->constr_x = slice->UNCONSTR; + slice->constr_y = slice->UNCONSTR; + slice->constr_z = slice->UNCONSTR; + slice->constr_parent = nullptr; + } + slice3->constr_children.clear(); + slice3->constr_abs_z = true; + slice3->constr_z = 0; + + slice2->constr_children.clear(); + slice2->constr_abs_z = true; + slice2->constr_z = 1; + slice2->constr_x = 0; + slice2->constr_y = 0; + slice2->constr_parent = slice3; + slice3->constr_children.push_back(slice2); + + slice1->constr_children.clear(); + slice1->constr_abs_z = true; + slice1->constr_z = 2; + slice1->constr_x = 0; + slice1->constr_y = 0; + slice1->constr_parent = slice3; + slice3->constr_children.push_back(slice1); + + slice0->constr_children.clear(); + slice0->constr_abs_z = true; + slice0->constr_z = 3; + slice0->constr_x = 0; + slice0->constr_y = 0; + slice0->constr_parent = slice3; + slice3->constr_children.push_back(slice0); + + if (lutffPairs.find(ci->name) != lutffPairs.end()) { + CellInfo *ff = ctx->cells.at(lutffPairs[ci->name]).get(); + ff_to_slice(ctx, ff, slice2, 1, true); + packed_cells.insert(ff->name); + sliceUsage[slice2->name].ff1_used = true; + lutffPairs.erase(ci->name); + fflutPairs.erase(ff->name); + } + + packed_cells.insert(ci->name); + } + } + flush_cells(); + } // Create a feed in to the carry chain CellInfo *make_carry_feed_in(NetInfo *carry, PortRef chain_in) { @@ -1183,7 +1334,7 @@ class Ecp5Packer pack_dram(); pack_carries(); find_lutff_pairs(); - pack_lut5s(); + pack_lut5xs(); pair_luts(); pack_lut_pairs(); pack_remaining_luts(); @@ -1252,6 +1403,10 @@ void Arch::assignArchInfo() ci->sliceInfo.clkmux = id(str_or_default(ci->params, id_CLKMUX, "CLK")); ci->sliceInfo.lsrmux = id(str_or_default(ci->params, id_LSRMUX, "LSR")); ci->sliceInfo.srmode = id(str_or_default(ci->params, id_SRMODE, "LSR_OVER_CE")); + ci->sliceInfo.has_l6mux = false; + if (ci->ports.count(id_FXA) && ci->ports[id_FXA].net != nullptr && + ci->ports[id_FXA].net->driver.port == id_OFX0) + ci->sliceInfo.has_l6mux = true; } } } diff --git a/ecp5/project.cc b/ecp5/project.cc index bca21643..43318b1c 100644 --- a/ecp5/project.cc +++ b/ecp5/project.cc @@ -45,7 +45,7 @@ std::unique_ptr ProjectHandler::createContext(pt::ptree &root) chipArgs.type = ArchArgs::LFE5U_85F; } chipArgs.package = root.get("project.arch.package"); - chipArgs.speed = root.get("project.arch.speed"); + chipArgs.speed = ArchArgs::SpeedGrade(root.get("project.arch.speed")); return std::unique_ptr(new Context(chipArgs)); } diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py index 99fe7ba9..cdd3bd06 100755 --- a/ecp5/trellis_import.py +++ b/ecp5/trellis_import.py @@ -3,6 +3,8 @@ import pytrellis import database import argparse import json +import pip_classes +import timing_dbs from os import path location_types = dict() @@ -135,34 +137,81 @@ def process_loc_globals(chip): spine = (-1, -1) global_data[x, y] = (quadrants.index(quad), int(tapdrv.dir), tapdrv.col, spine) -def get_wire_type(name): - if "H00" in name or "V00" in name: - return "X0" - if "H01" in name or "V01" in name: - return "X1" - if "H02" in name or "V02" in name: - return "X2" - if "H06" in name or "V06" in name: - return "X6" - if "_SLICE" in name or "_EBR" in name: - return "SLICE" - return "LOCAL" -def get_pip_delay(wire_from, wire_to): - # ECP5 timings WIP!!! - type_from = get_wire_type(wire_from) - type_to = get_wire_type(wire_to) - if type_from == "X2" and type_to == "X2": - return 170 - if type_from == "SLICE" or type_to == "SLICE": - return 205 - if type_from in ("LOCAL", "X0") and type_to in ("X1", "X2", "X6"): - return 90 - if type_from == "X6" or type_to == "X6": - return 200 - if type_from in ("X1", "X2", "X6") and type_to in ("LOCAL", "X0"): - return 90 - return 100 +speed_grade_names = ["6", "7", "8", "8_5G"] +speed_grade_cells = {} +speed_grade_pips = {} + +pip_class_to_idx = {"default": 0} + +timing_port_xform = { + "RAD0": "D0", + "RAD1": "B0", + "RAD2": "C0", + "RAD3": "A0", +} + + +def process_timing_data(): + for grade in speed_grade_names: + with open(timing_dbs.cells_db_path("ECP5", grade)) as f: + cell_data = json.load(f) + cells = [] + for cell, cdata in sorted(cell_data.items()): + celltype = constids[cell.replace(":", "_").replace("=", "_").replace(",", "_")] + delays = [] + setupholds = [] + for entry in cdata: + if entry["type"] == "Width": + continue + elif entry["type"] == "IOPath": + from_pin = entry["from_pin"][1] if type(entry["from_pin"]) is list else entry["from_pin"] + if from_pin in timing_port_xform: + from_pin = timing_port_xform[from_pin] + to_pin = entry["to_pin"] + if to_pin in timing_port_xform: + to_pin = timing_port_xform[to_pin] + min_delay = min(entry["rising"][0], entry["falling"][0]) + max_delay = min(entry["rising"][2], entry["falling"][2]) + delays.append((constids[from_pin], constids[to_pin], min_delay, max_delay)) + elif entry["type"] == "SetupHold": + pin = constids[entry["pin"]] + clock = constids[entry["clock"][1]] + min_setup = entry["setup"][0] + max_setup = entry["setup"][2] + min_hold = entry["hold"][0] + max_hold = entry["hold"][2] + setupholds.append((pin, clock, min_setup, max_setup, min_hold, max_hold)) + else: + assert False, entry["type"] + cells.append((celltype, delays, setupholds)) + pip_class_delays = [] + for i in range(len(pip_class_to_idx)): + pip_class_delays.append((50, 50, 0, 0)) + + with open(timing_dbs.interconnect_db_path("ECP5", grade)) as f: + interconn_data = json.load(f) + for pipclass, pipdata in sorted(interconn_data.items()): + + min_delay = pipdata["delay"][0] * 1.1 + max_delay = pipdata["delay"][2] * 1.1 + min_fanout = pipdata["fanout"][0] + max_fanout = pipdata["fanout"][2] + if grade == "6": + pip_class_to_idx[pipclass] = len(pip_class_delays) + pip_class_delays.append((min_delay, max_delay, min_fanout, max_fanout)) + else: + if pipclass in pip_class_to_idx: + pip_class_delays[pip_class_to_idx[pipclass]] = (min_delay, max_delay, min_fanout, max_fanout) + speed_grade_cells[grade] = cells + speed_grade_pips[grade] = pip_class_delays + + +def get_pip_class(wire_from, wire_to): + class_name = pip_classes.get_pip_class(wire_from, wire_to) + if class_name is None or class_name not in pip_class_to_idx: + class_name = "default" + return pip_class_to_idx[class_name] @@ -181,7 +230,7 @@ def write_database(dev_name, chip, ddrg, endianness): loc = loc_with_type[arc_loctype] lt = ddrg.typeAtLocation[pytrellis.Location(loc[0] + rel.x, loc[1] + rel.y)] wire = ddrg.locationTypes[lt].wires[idx] - return ddrg.to_str(wire.name) + return "R{}C{}_{}".format(loc[1] + rel.y, loc[0] + rel.x, ddrg.to_str(wire.name)) bba = BinaryBlobAssembler() bba.pre('#include "nextpnr.h"') @@ -202,7 +251,7 @@ def write_database(dev_name, chip, ddrg, endianness): bba.u32(arc.sinkWire.id, "dst_idx") src_name = get_wire_name(idx, arc.srcWire.rel, arc.srcWire.id) snk_name = get_wire_name(idx, arc.sinkWire.rel, arc.sinkWire.id) - bba.u32(get_pip_delay(src_name, snk_name), "delay") # TODO:delay + bba.u32(get_pip_class(src_name, snk_name), "timing_class") bba.u16(get_tiletype_index(ddrg.to_str(arc.tiletype)), "tile_type") cls = arc.cls if cls == 1 and "PCS" in snk_name or "DCU" in snk_name or "DCU" in src_name: @@ -321,11 +370,53 @@ def write_database(dev_name, chip, ddrg, endianness): bba.u16(bank, "bank") bba.u16(0, "padding") - bba.l("tiletype_names", "RelPtr") for tt, idx in sorted(tiletype_names.items(), key=lambda x: x[1]): bba.s(tt, "name") + for grade in speed_grade_names: + for cell in speed_grade_cells[grade]: + celltype, delays, setupholds = cell + if len(delays) > 0: + bba.l("cell_%d_delays_%s" % (celltype, grade)) + for delay in delays: + from_pin, to_pin, min_delay, max_delay = delay + bba.u32(from_pin, "from_pin") + bba.u32(to_pin, "to_pin") + bba.u32(min_delay, "min_delay") + bba.u32(max_delay, "max_delay") + if len(setupholds) > 0: + bba.l("cell_%d_setupholds_%s" % (celltype, grade)) + for sh in setupholds: + pin, clock, min_setup, max_setup, min_hold, max_hold = sh + bba.u32(pin, "sig_port") + bba.u32(clock, "clock_port") + bba.u32(min_setup, "min_setup") + bba.u32(max_setup, "max_setup") + bba.u32(min_hold, "min_hold") + bba.u32(max_hold, "max_hold") + bba.l("cell_timing_data_%s" % grade) + for cell in speed_grade_cells[grade]: + celltype, delays, setupholds = cell + bba.u32(celltype, "cell_type") + bba.u32(len(delays), "num_delays") + bba.u32(len(setupholds), "num_setup_hold") + bba.r("cell_%d_delays_%s" % (celltype, grade) if len(delays) > 0 else None, "delays") + bba.r("cell_%d_setupholds_%s" % (celltype, grade) if len(delays) > 0 else None, "setupholds") + bba.l("pip_timing_data_%s" % grade) + for pipclass in speed_grade_pips[grade]: + min_delay, max_delay, min_fanout, max_fanout = pipclass + bba.u32(min_delay, "min_delay") + bba.u32(max_delay, "max_delay") + bba.u32(min_fanout, "min_fanout") + bba.u32(max_fanout, "max_fanout") + bba.l("speed_grade_data") + for grade in speed_grade_names: + bba.u32(len(speed_grade_cells[grade]), "num_cell_timings") + bba.u32(len(speed_grade_pips[grade]), "num_pip_classes") + bba.r("cell_timing_data_%s" % grade, "cell_timings") + bba.r("pip_timing_data_%s" % grade, "pip_classes") + bba.l("chip_info") bba.u32(max_col + 1, "width") bba.u32(max_row + 1, "height") @@ -341,6 +432,7 @@ def write_database(dev_name, chip, ddrg, endianness): bba.r("package_data", "package_info") bba.r("pio_info", "pio_info") bba.r("tiles_info", "tile_info") + bba.r("speed_grade_data", "speed_grades") bba.pop() return bba @@ -375,6 +467,7 @@ def main(): ddrg = pytrellis.make_dedup_chipdb(chip) max_row = chip.get_max_row() max_col = chip.get_max_col() + process_timing_data() process_pio_db(ddrg, args.device) process_loc_globals(chip) # print("{} unique location types".format(len(ddrg.locationTypes))) diff --git a/gui/basewindow.cc b/gui/basewindow.cc index 92812285..49c2d8d5 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -44,7 +44,6 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr context, ArchArgs args, initBasenameResource(); qRegisterMetaType(); - log_files.clear(); log_streams.clear(); setObjectName("BaseMainWindow"); diff --git a/ice40/arch.h b/ice40/arch.h index e8c597c9..10255dbe 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -884,6 +884,13 @@ struct Arch : BaseCtx } NPNR_ASSERT_FALSE("Expected PLL pin to share an output with an SB_IO D_IN_{0,1}"); } + + int getDrivenGlobalNetwork(BelId bel) const + { + NPNR_ASSERT(getBelType(bel) == id_SB_GB); + IdString glb_net = getWireName(getBelPinWire(bel, id_GLOBAL_BUFFER_OUTPUT)); + return std::stoi(std::string("") + glb_net.str(this).back()); + } }; void ice40DelayFuzzerMain(Context *ctx); diff --git a/ice40/arch_place.cc b/ice40/arch_place.cc index 41f9b640..90bb62b9 100644 --- a/ice40/arch_place.cc +++ b/ice40/arch_place.cc @@ -165,8 +165,7 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const return true; NPNR_ASSERT(cell->ports.at(id_GLOBAL_BUFFER_OUTPUT).net != nullptr); const NetInfo *net = cell->ports.at(id_GLOBAL_BUFFER_OUTPUT).net; - IdString glb_net = getWireName(getBelPinWire(bel, id_GLOBAL_BUFFER_OUTPUT)); - int glb_id = std::stoi(std::string("") + glb_net.str(this).back()); + int glb_id = getDrivenGlobalNetwork(bel); if (net->is_reset && net->is_enable) return false; else if (net->is_reset) diff --git a/ice40/arch_pybindings.h b/ice40/arch_pybindings.h index 070c2396..eaf3ac97 100644 --- a/ice40/arch_pybindings.h +++ b/ice40/arch_pybindings.h @@ -45,14 +45,36 @@ template <> struct string_converter { WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); } - std::string to_str(Context *ctx, WireId id) { return ctx->getWireName(id).str(ctx); } + std::string to_str(Context *ctx, WireId id) + { + if (id == WireId()) + throw bad_wrap(); + return ctx->getWireName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); } + + std::string to_str(Context *ctx, WireId id) + { + if (id == WireId()) + throw bad_wrap(); + return ctx->getWireName(id).str(ctx); + } }; template <> struct string_converter { PipId from_str(Context *ctx, std::string name) { return ctx->getPipByName(ctx->id(name)); } - std::string to_str(Context *ctx, PipId id) { return ctx->getPipName(id).str(ctx); } + std::string to_str(Context *ctx, PipId id) + { + if (id == PipId()) + throw bad_wrap(); + return ctx->getPipName(id).str(ctx); + } }; } // namespace PythonConversion diff --git a/ice40/pack.cc b/ice40/pack.cc index dae19b2d..fc28121e 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -587,10 +587,36 @@ static void promote_globals(Context *ctx) } } int prom_globals = 0, prom_resets = 0, prom_cens = 0, prom_logics = 0; - int gbs_available = 8; + int gbs_available = 8, resets_available = 4, cens_available = 4; for (auto &cell : ctx->cells) - if (is_gbuf(ctx, cell.second.get())) + if (is_gbuf(ctx, cell.second.get())) { + /* One less buffer available */ --gbs_available; + + /* And possibly limits what we can promote */ + if (cell.second->attrs.find(ctx->id("BEL")) != cell.second->attrs.end()) { + /* If the SB_GB is locked, doesn't matter what it drives */ + BelId bel = ctx->getBelByName(ctx->id(cell.second->attrs[ctx->id("BEL")])); + int glb_id = ctx->getDrivenGlobalNetwork(bel); + if ((glb_id % 2) == 0) + resets_available--; + else if ((glb_id % 2) == 1) + cens_available--; + } else { + /* If it's free to move around, then look at what it drives */ + NetInfo *ni = cell.second->ports[id_GLOBAL_BUFFER_OUTPUT].net; + + for (auto user : ni->users) { + if (is_reset_port(ctx, user)) { + resets_available--; + break; + } else if (is_enable_port(ctx, user)) { + cens_available--; + break; + } + } + } + } while (prom_globals < gbs_available) { auto global_clock = std::max_element(clock_count.begin(), clock_count.end(), [](const std::pair &a, const std::pair &b) { @@ -610,8 +636,8 @@ static void promote_globals(Context *ctx) return a.second < b.second; }); if (global_clock->second == 0 && prom_logics < 4 && global_logic->second > logic_fanout_thresh && - (global_logic->second > global_cen->second || prom_cens >= 4) && - (global_logic->second > global_reset->second || prom_resets >= 4)) { + (global_logic->second > global_cen->second || prom_cens >= cens_available) && + (global_logic->second > global_reset->second || prom_resets >= resets_available)) { NetInfo *logicnet = ctx->nets[global_logic->first].get(); insert_global(ctx, logicnet, false, false, true); ++prom_globals; @@ -620,7 +646,7 @@ static void promote_globals(Context *ctx) reset_count.erase(logicnet->name); cen_count.erase(logicnet->name); logic_count.erase(logicnet->name); - } else if (global_reset->second > global_clock->second && prom_resets < 4) { + } else if (global_reset->second > global_clock->second && prom_resets < resets_available) { NetInfo *rstnet = ctx->nets[global_reset->first].get(); insert_global(ctx, rstnet, true, false, false); ++prom_globals; @@ -629,7 +655,7 @@ static void promote_globals(Context *ctx) reset_count.erase(rstnet->name); cen_count.erase(rstnet->name); logic_count.erase(rstnet->name); - } else if (global_cen->second > global_clock->second && prom_cens < 4 && + } else if (global_cen->second > global_clock->second && prom_cens < cens_available && global_cen->second > enable_fanout_thresh) { NetInfo *cennet = ctx->nets[global_cen->first].get(); insert_global(ctx, cennet, false, true, false); @@ -874,7 +900,7 @@ static void pack_special(Context *ctx) newname = "PLLOUT_A"; if (pi.name == ctx->id("PLLOUTCOREB")) newname = "PLLOUT_B"; - if (pi.name == ctx->id("PLLOUTGLOBALA") || pi.name == ctx->id("PLLOUTGLOBALA")) + if (pi.name == ctx->id("PLLOUTGLOBALA") || pi.name == ctx->id("PLLOUTGLOBAL")) newname = "PLLOUT_A_GLOBAL"; if (pi.name == ctx->id("PLLOUTGLOBALB")) newname = "PLLOUT_B_GLOBAL"; @@ -987,6 +1013,8 @@ static void pack_special(Context *ctx) for (auto user : pad_packagepin_net->users) { user.cell->ports.erase(user.port); } + if (pad_packagepin_net->driver.cell != nullptr) + pad_packagepin_net->driver.cell->ports.erase(pad_packagepin_net->driver.port); ctx->nets.erase(pad_packagepin_net->name); pad_packagepin_net = nullptr; } diff --git a/ice40/pcf.cc b/ice40/pcf.cc index af5b3e17..ce453af9 100644 --- a/ice40/pcf.cc +++ b/ice40/pcf.cc @@ -33,7 +33,9 @@ bool apply_pcf(Context *ctx, std::string filename, std::istream &in) if (!in) log_error("failed to open PCF file\n"); std::string line; + int lineno = 0; while (std::getline(in, line)) { + lineno++; size_t cstart = line.find("#"); if (cstart != std::string::npos) line = line.substr(0, cstart); @@ -49,21 +51,25 @@ bool apply_pcf(Context *ctx, std::string filename, std::istream &in) size_t args_end = 1; while (args_end < words.size() && words.at(args_end).at(0) == '-') args_end++; + if (args_end >= words.size() - 1) + log_error("expected PCF syntax 'set_io cell pin' (on line %d)\n", lineno); std::string cell = words.at(args_end); std::string pin = words.at(args_end + 1); auto fnd_cell = ctx->cells.find(ctx->id(cell)); if (fnd_cell == ctx->cells.end()) { - log_warning("unmatched pcf constraint %s\n", cell.c_str()); + log_warning("unmatched constraint '%s' (on line %d)\n", cell.c_str(), lineno); } else { BelId pin_bel = ctx->getPackagePinBel(pin); if (pin_bel == BelId()) - log_error("package does not have a pin named %s\n", pin.c_str()); + log_error("package does not have a pin named '%s' (on line %d)\n", pin.c_str(), lineno); + if (fnd_cell->second->attrs.count(ctx->id("BEL"))) + log_error("duplicate pin constraint on '%s' (on line %d)\n", cell.c_str(), lineno); fnd_cell->second->attrs[ctx->id("BEL")] = ctx->getBelName(pin_bel).str(ctx); log_info("constrained '%s' to bel '%s'\n", cell.c_str(), fnd_cell->second->attrs[ctx->id("BEL")].c_str()); } } else { - log_error("unsupported pcf command '%s'\n", cmd.c_str()); + log_error("unsupported PCF command '%s' (on line %d)\n", cmd.c_str(), lineno); } } ctx->settings.emplace(ctx->id("input/pcf"), filename); diff --git a/json/jsonparse.cc b/json/jsonparse.cc index e7f39a19..bddbee0b 100644 --- a/json/jsonparse.cc +++ b/json/jsonparse.cc @@ -472,12 +472,7 @@ void json_import_ports(Context *ctx, const string &modname, const std::vectordata_string.compare(string("x")) == 0) { - ground_net(ctx, net.get()); - log_info(" Floating wire node value, " - "'%s' on '%s'/'%s', converted to zero driver\n", - this_port.name.c_str(ctx), modname.c_str(), obj_name.c_str()); - } else log_error(" Unknown fixed type wire node " "value, \'%s\'\n",