diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 00000000..10865c7b --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,11 @@ +task: + name: build-test-ubuntu1604 + container: + cpu: 4 + memory: 16 + dockerfile: .cirrus/Dockerfile.ubuntu16.04 + + build_script: mkdir build && cd build && cmake .. -DARCH=all -DTRELLIS_ROOT=/usr/local/src/prjtrellis -DBUILD_TESTS=on && make -j $(nproc) + test_generic_script: cd build && ./nextpnr-generic-test + test_ice40_script: cd build && ./nextpnr-ice40-test + test_ecp5_script: cd build && ./nextpnr-ecp5-test diff --git a/.cirrus/Dockerfile.ubuntu16.04 b/.cirrus/Dockerfile.ubuntu16.04 new file mode 100644 index 00000000..7335292f --- /dev/null +++ b/.cirrus/Dockerfile.ubuntu16.04 @@ -0,0 +1,33 @@ +FROM ubuntu:xenial-20181113 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN set -e -x ;\ + apt-get -y update ;\ + apt-get -y upgrade ;\ + apt-get -y install \ + build-essential cmake clang python3-dev libboost-all-dev qt5-default git + +RUN set -e -x ;\ + apt-get -y install \ + libftdi-dev pkg-config + +RUN set -e -x ;\ + mkdir -p /usr/local/src ;\ + cd /usr/local/src ;\ + git clone --recursive https://github.com/cliffordwolf/icestorm.git ;\ + cd icestorm ;\ + git reset --hard 9671b760f84ca4006f0ef101a3e3b201df4eabb5 ;\ + make -j $(nproc) ;\ + make install + +RUN set -e -x ;\ + mkdir -p /usr/local/src ;\ + cd /usr/local/src ;\ + git clone --recursive https://github.com/SymbiFlow/prjtrellis.git ;\ + cd prjtrellis ;\ + git reset --hard de035a6e5e5818804a66b9408f0ad381b10957db ;\ + cd libtrellis ;\ + cmake -DCMAKE_INSTALL_PREFIX=/usr . ;\ + make -j $(nproc) ;\ + make install diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d6cd312..c75a32fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,8 +48,8 @@ set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /W4 /wd4100 /wd4244 /wd4125 /wd4800 /wd4456 /wd4458 /wd4305 /wd4459 /wd4121 /wd4996") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /W4 /wd4100 /wd4244 /wd4125 /wd4800 /wd4456 /wd4458 /wd4305 /wd4459 /wd4121 /wd4996 /wd4127") else() -set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fPIC -ggdb") -set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g") +set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fPIC -ggdb -pipe") +set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe") endif() set(CMAKE_DEFIN) diff --git a/common/command.cc b/common/command.cc index 206a4d30..d332375a 100644 --- a/common/command.cc +++ b/common/command.cc @@ -131,9 +131,9 @@ void CommandHandler::setupContext(Context *ctx) } if (vm.count("quiet")) { - log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING)); + log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING_MSG)); } else { - log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG)); + log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG_MSG)); } if (vm.count("log")) { @@ -141,7 +141,7 @@ void CommandHandler::setupContext(Context *ctx) 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)); + log_streams.push_back(std::make_pair(&logfile, LogLevel::LOG_MSG)); } if (vm.count("force")) { @@ -285,8 +285,8 @@ 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); + 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"); diff --git a/common/log.cc b/common/log.cc index 0a75b020..01aec79a 100644 --- a/common/log.cc +++ b/common/log.cc @@ -84,7 +84,7 @@ std::string vstringf(const char *fmt, va_list ap) return string; } -void logv(const char *format, va_list ap, LogLevel level = LogLevel::LOG) +void logv(const char *format, va_list ap, LogLevel level = LogLevel::LOG_MSG) { // // Trim newlines from the beginning @@ -132,7 +132,7 @@ void log_always(const char *format, ...) { va_list ap; va_start(ap, format); - logv(format, ap, LogLevel::ALWAYS); + logv(format, ap, LogLevel::ALWAYS_MSG); va_end(ap); } @@ -140,7 +140,7 @@ void log(const char *format, ...) { va_list ap; va_start(ap, format); - logv(format, ap, LogLevel::LOG); + logv(format, ap, LogLevel::LOG_MSG); va_end(ap); } @@ -148,7 +148,7 @@ void log_info(const char *format, ...) { va_list ap; va_start(ap, format); - logv_prefixed("Info: ", format, ap, LogLevel::INFO); + logv_prefixed("Info: ", format, ap, LogLevel::INFO_MSG); va_end(ap); } @@ -156,7 +156,7 @@ void log_warning(const char *format, ...) { va_list ap; va_start(ap, format); - logv_prefixed("Warning: ", format, ap, LogLevel::WARNING); + logv_prefixed("Warning: ", format, ap, LogLevel::WARNING_MSG); va_end(ap); } @@ -164,7 +164,7 @@ void log_error(const char *format, ...) { va_list ap; va_start(ap, format); - logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR); + logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG); if (log_error_atexit) log_error_atexit(); @@ -184,7 +184,7 @@ void log_nonfatal_error(const char *format, ...) { va_list ap; va_start(ap, format); - logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR); + logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG); va_end(ap); had_nonfatal_error = true; } diff --git a/common/log.h b/common/log.h index 52158f18..5745df0d 100644 --- a/common/log.h +++ b/common/log.h @@ -44,11 +44,11 @@ struct log_execution_error_exception enum class LogLevel { - LOG, - INFO, - WARNING, - ERROR, - ALWAYS + LOG_MSG, + INFO_MSG, + WARNING_MSG, + ERROR_MSG, + ALWAYS_MSG }; extern std::vector> log_streams; diff --git a/common/rulecheck.cc b/common/rulecheck.cc index c696e548..1db9ae00 100644 --- a/common/rulecheck.cc +++ b/common/rulecheck.cc @@ -9,7 +9,7 @@ bool check_all_nets_driven(Context *ctx) { const bool debug = false; - log_info("Rule checker, Verifying pre-placed design\n"); + log_info("Rule checker, verifying imported design\n"); for (auto &cell_entry : ctx->cells) { CellInfo *cell = cell_entry.second.get(); diff --git a/common/timing.cc b/common/timing.cc index 88ab14c2..b15327fb 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -86,6 +86,7 @@ struct CriticalPath }; typedef std::unordered_map CriticalPathMap; +typedef std::unordered_map NetCriticalityMap; struct Timing { @@ -95,6 +96,7 @@ struct Timing delay_t min_slack; CriticalPathMap *crit_path; DelayFrequency *slack_histogram; + NetCriticalityMap *net_crit; IdString async_clock; struct TimingData @@ -105,13 +107,15 @@ struct Timing unsigned max_path_length = 0; delay_t min_remaining_budget; bool false_startpoint = false; + std::vector min_required; std::unordered_map arrival_time; }; Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr, - DelayFrequency *slack_histogram = nullptr) + DelayFrequency *slack_histogram = nullptr, NetCriticalityMap *net_crit = nullptr) : ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->target_freq), - crit_path(crit_path), slack_histogram(slack_histogram), async_clock(ctx->id("$async$")) + crit_path(crit_path), slack_histogram(slack_histogram), net_crit(net_crit), + async_clock(ctx->id("$async$")) { } @@ -454,6 +458,180 @@ struct Timing std::reverse(cp_ports.begin(), cp_ports.end()); } } + + if (net_crit) { + NPNR_ASSERT(crit_path); + // Go through in reverse topographical order to set required times + for (auto net : boost::adaptors::reverse(topographical_order)) { + if (!net_data.count(net)) + continue; + auto &nd_map = net_data.at(net); + for (auto &startdomain : nd_map) { + auto &nd = startdomain.second; + if (nd.false_startpoint) + continue; + if (startdomain.first.clock == async_clock) + continue; + if (nd.min_required.empty()) + nd.min_required.resize(net->users.size(), std::numeric_limits::max()); + delay_t net_min_required = std::numeric_limits::max(); + for (size_t i = 0; i < net->users.size(); i++) { + auto &usr = net->users.at(i); + auto net_delay = ctx->getNetinfoRouteDelay(net, usr); + int port_clocks; + TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks); + if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) { + auto process_endpoint = [&](IdString clksig, ClockEdge edge, delay_t setup) { + delay_t period; + // Set default period + if (edge == startdomain.first.edge) { + period = clk_period; + } else { + period = clk_period / 2; + } + if (clksig != async_clock) { + if (ctx->nets.at(clksig)->clkconstr) { + if (edge == startdomain.first.edge) { + // same edge + period = ctx->nets.at(clksig)->clkconstr->period.minDelay(); + } else if (edge == RISING_EDGE) { + // falling -> rising + period = ctx->nets.at(clksig)->clkconstr->low.minDelay(); + } else if (edge == FALLING_EDGE) { + // rising -> falling + period = ctx->nets.at(clksig)->clkconstr->high.minDelay(); + } + } + } + nd.min_required.at(i) = std::min(period - setup, nd.min_required.at(i)); + }; + if (portClass == TMG_REGISTER_INPUT) { + for (int j = 0; j < port_clocks; j++) { + TimingClockingInfo clkInfo = ctx->getPortClockingInfo(usr.cell, usr.port, j); + const NetInfo *clknet = get_net_or_empty(usr.cell, clkInfo.clock_port); + IdString clksig = clknet ? clknet->name : async_clock; + process_endpoint(clksig, clknet ? clkInfo.edge : RISING_EDGE, + clkInfo.setup.maxDelay()); + } + } else { + process_endpoint(async_clock, RISING_EDGE, 0); + } + } + net_min_required = std::min(net_min_required, nd.min_required.at(i) - net_delay); + } + PortRef &drv = net->driver; + if (drv.cell == nullptr) + continue; + for (const auto &port : drv.cell->ports) { + if (port.second.type != PORT_IN || !port.second.net) + continue; + DelayInfo comb_delay; + bool is_path = ctx->getCellDelay(drv.cell, port.first, drv.port, comb_delay); + if (!is_path) + continue; + int cc; + auto pclass = ctx->getPortTimingClass(drv.cell, port.first, cc); + if (pclass != TMG_COMB_INPUT) + continue; + NetInfo *sink_net = port.second.net; + if (net_data.count(sink_net) && net_data.at(sink_net).count(startdomain.first)) { + auto &sink_nd = net_data.at(sink_net).at(startdomain.first); + if (sink_nd.min_required.empty()) + sink_nd.min_required.resize(sink_net->users.size(), + std::numeric_limits::max()); + for (size_t i = 0; i < sink_net->users.size(); i++) { + auto &user = sink_net->users.at(i); + if (user.cell == drv.cell && user.port == port.first) { + sink_nd.min_required.at(i) = net_min_required - comb_delay.maxDelay(); + break; + } + } + } + } + } + } + std::unordered_map worst_slack; + + // Assign slack values + for (auto &net_entry : net_data) { + const NetInfo *net = net_entry.first; + for (auto &startdomain : net_entry.second) { + auto &nd = startdomain.second; + if (startdomain.first.clock == async_clock) + continue; + if (nd.min_required.empty()) + continue; + auto &nc = (*net_crit)[net->name]; + if (nc.slack.empty()) + nc.slack.resize(net->users.size(), std::numeric_limits::max()); +#if 0 + if (ctx->debug) + log_info("Net %s cd %s\n", net->name.c_str(ctx), startdomain.first.clock.c_str(ctx)); +#endif + for (size_t i = 0; i < net->users.size(); i++) { + delay_t slack = nd.min_required.at(i) - + (nd.max_arrival + ctx->getNetinfoRouteDelay(net, net->users.at(i))); +#if 0 + if (ctx->debug) + log_info(" user %s.%s required %.02fns arrival %.02f route %.02f slack %.02f\n", + net->users.at(i).cell->name.c_str(ctx), net->users.at(i).port.c_str(ctx), + ctx->getDelayNS(nd.min_required.at(i)), ctx->getDelayNS(nd.max_arrival), + ctx->getDelayNS(ctx->getNetinfoRouteDelay(net, net->users.at(i))), ctx->getDelayNS(slack)); +#endif + if (worst_slack.count(startdomain.first)) + worst_slack.at(startdomain.first) = std::min(worst_slack.at(startdomain.first), slack); + else + worst_slack[startdomain.first] = slack; + nc.slack.at(i) = slack; + } + if (ctx->debug) + log_break(); + } + } + // Assign criticality values + for (auto &net_entry : net_data) { + const NetInfo *net = net_entry.first; + for (auto &startdomain : net_entry.second) { + if (startdomain.first.clock == async_clock) + continue; + auto &nd = startdomain.second; + if (nd.min_required.empty()) + continue; + auto &nc = (*net_crit)[net->name]; + if (nc.slack.empty()) + continue; + if (nc.criticality.empty()) + nc.criticality.resize(net->users.size(), 0); + // Only consider intra-clock paths for criticality + if (!crit_path->count(ClockPair{startdomain.first, startdomain.first})) + continue; + delay_t dmax = crit_path->at(ClockPair{startdomain.first, startdomain.first}).path_delay; + for (size_t i = 0; i < net->users.size(); i++) { + float criticality = 1.0f - (float(nc.slack.at(i) - worst_slack.at(startdomain.first)) / dmax); + nc.criticality.at(i) = criticality; + } + nc.max_path_length = nd.max_path_length; + nc.cd_worst_slack = worst_slack.at(startdomain.first); + } + } +#if 0 + if (ctx->debug) { + for (auto &nc : *net_crit) { + NetInfo *net = ctx->nets.at(nc.first).get(); + log_info("Net %s maxlen %d worst_slack %.02fns: \n", nc.first.c_str(ctx), nc.second.max_path_length, + ctx->getDelayNS(nc.second.cd_worst_slack)); + if (!nc.second.criticality.empty() && !nc.second.slack.empty()) { + for (size_t i = 0; i < net->users.size(); i++) { + log_info(" user %s.%s slack %.02fns crit %.03f\n", net->users.at(i).cell->name.c_str(ctx), + net->users.at(i).port.c_str(ctx), ctx->getDelayNS(nc.second.slack.at(i)), + nc.second.criticality.at(i)); + } + } + log_break(); + } + } +#endif + } return min_slack; } @@ -766,4 +944,12 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p } } +void get_criticalities(Context *ctx, NetCriticalityMap *net_crit) +{ + CriticalPathMap crit_paths; + net_crit->clear(); + Timing timing(ctx, true, true, &crit_paths, nullptr, net_crit); + timing.walk_paths(); +} + NEXTPNR_NAMESPACE_END diff --git a/common/timing.h b/common/timing.h index 42f928dc..f1d18e8a 100644 --- a/common/timing.h +++ b/common/timing.h @@ -32,6 +32,19 @@ void assign_budget(Context *ctx, bool quiet = false); void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false, bool warn_on_failure = false); +// Data for the timing optimisation algorithm +struct NetCriticalityInfo +{ + // One each per user + std::vector slack; + std::vector criticality; + unsigned max_path_length = 0; + delay_t cd_worst_slack = std::numeric_limits::max(); +}; + +typedef std::unordered_map NetCriticalityMap; +void get_criticalities(Context *ctx, NetCriticalityMap *net_crit); + NEXTPNR_NAMESPACE_END #endif diff --git a/common/timing_opt.cc b/common/timing_opt.cc new file mode 100644 index 00000000..a451bfa0 --- /dev/null +++ b/common/timing_opt.cc @@ -0,0 +1,623 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * 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. + * + */ + +/* + * Timing-optimised detailed placement algorithm using BFS of the neighbour graph created from cells + * on a critical path + * + * Based on "An Effective Timing-Driven Detailed Placement Algorithm for FPGAs" + * https://www.cerc.utexas.edu/utda/publications/C205.pdf + * + * Modifications made to deal with the smaller Bels that nextpnr uses instead of swapping whole tiles, + * and deal with the fact that not every cell on the crit path may be swappable. + */ + +#include "timing_opt.h" +#include +#include +#include "nextpnr.h" +#include "timing.h" +#include "util.h" + +namespace std { + +template <> struct hash> +{ + std::size_t + operator()(const std::pair &idp) const + noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(idp.first)); + boost::hash_combine(seed, hash()(idp.second)); + return seed; + } +}; + +template <> struct hash> +{ + std::size_t operator()(const std::pair &idp) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(idp.first)); + boost::hash_combine(seed, hash()(idp.second)); + return seed; + } +}; + +template <> struct hash> +{ + std::size_t + operator()(const std::pair &idp) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(idp.first)); + boost::hash_combine(seed, hash()(idp.second)); + return seed; + } +}; +} // namespace std + +NEXTPNR_NAMESPACE_BEGIN + +class TimingOptimiser +{ + public: + TimingOptimiser(Context *ctx, TimingOptCfg cfg) : ctx(ctx), cfg(cfg){}; + bool optimise() + { + log_info("Running timing-driven placement optimisation...\n"); + if (ctx->verbose) + timing_analysis(ctx, false, true, false, false); + for (int i = 0; i < 30; i++) { + log_info(" Iteration %d...\n", i); + get_criticalities(ctx, &net_crit); + setup_delay_limits(); + auto crit_paths = find_crit_paths(0.98, 50000); + for (auto &path : crit_paths) + optimise_path(path); + if (ctx->verbose) + timing_analysis(ctx, false, true, false, false); + } + return true; + } + + private: + void setup_delay_limits() + { + max_net_delay.clear(); + for (auto net : sorted(ctx->nets)) { + NetInfo *ni = net.second; + for (auto usr : ni->users) { + max_net_delay[std::make_pair(usr.cell->name, usr.port)] = std::numeric_limits::max(); + } + if (!net_crit.count(net.first)) + continue; + auto &nc = net_crit.at(net.first); + if (nc.slack.empty()) + continue; + for (size_t i = 0; i < ni->users.size(); i++) { + auto &usr = ni->users.at(i); + delay_t net_delay = ctx->getNetinfoRouteDelay(ni, usr); + if (nc.max_path_length != 0) { + max_net_delay[std::make_pair(usr.cell->name, usr.port)] = + net_delay + ((nc.slack.at(i) - nc.cd_worst_slack) / 10); + } + } + } + } + + bool check_cell_delay_limits(CellInfo *cell) + { + for (const auto &port : cell->ports) { + int nc; + if (ctx->getPortTimingClass(cell, port.first, nc) == TMG_IGNORE) + continue; + NetInfo *net = port.second.net; + if (net == nullptr) + continue; + if (port.second.type == PORT_IN) { + if (net->driver.cell == nullptr || net->driver.cell->bel == BelId()) + continue; + for (auto user : net->users) { + if (user.cell == cell && user.port == port.first) { + if (ctx->predictDelay(net, user) > + 1.1 * max_net_delay.at(std::make_pair(cell->name, port.first))) + return false; + } + } + + } else if (port.second.type == PORT_OUT) { + for (auto user : net->users) { + // This could get expensive for high-fanout nets?? + BelId dstBel = user.cell->bel; + if (dstBel == BelId()) + continue; + if (ctx->predictDelay(net, user) > + 1.1 * max_net_delay.at(std::make_pair(user.cell->name, user.port))) { + + return false; + } + } + } + } + return true; + } + + BelId cell_swap_bel(CellInfo *cell, BelId newBel) + { + BelId oldBel = cell->bel; + if (oldBel == newBel) + return oldBel; + CellInfo *other_cell = ctx->getBoundBelCell(newBel); + NPNR_ASSERT(other_cell == nullptr || other_cell->belStrength <= STRENGTH_WEAK); + ctx->unbindBel(oldBel); + if (other_cell != nullptr) { + ctx->unbindBel(newBel); + ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK); + } + ctx->bindBel(newBel, cell, STRENGTH_WEAK); + return oldBel; + } + + // Check that a series of moves are both legal and remain within maximum delay bounds + // Moves are specified as a vector of pairs + bool acceptable_move(std::vector> &move, bool check_delays = true) + { + for (auto &entry : move) { + if (!ctx->isBelLocationValid(entry.first->bel)) + return false; + if (!ctx->isBelLocationValid(entry.second)) + return false; + if (!check_delays) + continue; + if (!check_cell_delay_limits(entry.first)) + return false; + // We might have swapped another cell onto the original bel. Check this for max delay violations + // too + CellInfo *swapped = ctx->getBoundBelCell(entry.second); + if (swapped != nullptr && !check_cell_delay_limits(swapped)) + return false; + } + return true; + } + + int find_neighbours(CellInfo *cell, IdString prev_cell, int d, bool allow_swap) + { + BelId curr = cell->bel; + Loc curr_loc = ctx->getBelLocation(curr); + int found_count = 0; + cell_neighbour_bels[cell->name] = std::unordered_set{}; + for (int dy = -d; dy <= d; dy++) { + for (int dx = -d; dx <= d; dx++) { + // Go through all the Bels at this location + // First, find all bels of the correct type that are either unbound or bound normally + // Strongly bound bels are ignored + // FIXME: This means that we cannot touch carry chains or similar relatively constrained macros + std::vector free_bels_at_loc; + std::vector bound_bels_at_loc; + for (auto bel : ctx->getBelsByTile(curr_loc.x + dx, curr_loc.y + dy)) { + if (ctx->getBelType(bel) != cell->type) + continue; + CellInfo *bound = ctx->getBoundBelCell(bel); + if (bound == nullptr) { + free_bels_at_loc.push_back(bel); + } else if (bound->belStrength <= STRENGTH_WEAK && bound->constr_parent == nullptr && + bound->constr_children.empty()) { + bound_bels_at_loc.push_back(bel); + } + } + BelId candidate; + + while (!free_bels_at_loc.empty() || !bound_bels_at_loc.empty()) { + BelId try_bel; + if (!free_bels_at_loc.empty()) { + int try_idx = ctx->rng(int(free_bels_at_loc.size())); + try_bel = free_bels_at_loc.at(try_idx); + free_bels_at_loc.erase(free_bels_at_loc.begin() + try_idx); + } else { + int try_idx = ctx->rng(int(bound_bels_at_loc.size())); + try_bel = bound_bels_at_loc.at(try_idx); + bound_bels_at_loc.erase(bound_bels_at_loc.begin() + try_idx); + } + if (bel_candidate_cells.count(try_bel) && !allow_swap) { + // Overlap is only allowed if it is with the previous cell (this is handled by removing those + // edges in the graph), or if allow_swap is true to deal with cases where overlap means few + // neighbours are identified + if (bel_candidate_cells.at(try_bel).size() > 1 || + (bel_candidate_cells.at(try_bel).size() == 1 && + *(bel_candidate_cells.at(try_bel).begin()) != prev_cell)) + continue; + } + // TODO: what else to check here? + candidate = try_bel; + break; + } + + if (candidate != BelId()) { + cell_neighbour_bels[cell->name].insert(candidate); + bel_candidate_cells[candidate].insert(cell->name); + // Work out if we need to delete any overlap + std::vector overlap; + for (auto other : bel_candidate_cells[candidate]) + if (other != cell->name && other != prev_cell) + overlap.push_back(other); + if (overlap.size() > 0) + NPNR_ASSERT(allow_swap); + for (auto ov : overlap) { + bel_candidate_cells[candidate].erase(ov); + cell_neighbour_bels[ov].erase(candidate); + } + } + } + } + return found_count; + } + + std::vector> find_crit_paths(float crit_thresh, size_t max_count) + { + std::vector> crit_paths; + std::vector> crit_nets; + std::vector netnames; + std::transform(ctx->nets.begin(), ctx->nets.end(), std::back_inserter(netnames), + [](const std::pair> &kv) { return kv.first; }); + ctx->sorted_shuffle(netnames); + for (auto net : netnames) { + if (crit_nets.size() >= max_count) + break; + if (!net_crit.count(net)) + continue; + auto crit_user = std::max_element(net_crit[net].criticality.begin(), net_crit[net].criticality.end()); + if (*crit_user > crit_thresh) + crit_nets.push_back( + std::make_pair(ctx->nets[net].get(), crit_user - net_crit[net].criticality.begin())); + } + + auto port_user_index = [](CellInfo *cell, PortInfo &port) -> size_t { + NPNR_ASSERT(port.net != nullptr); + for (size_t i = 0; i < port.net->users.size(); i++) { + auto &usr = port.net->users.at(i); + if (usr.cell == cell && usr.port == port.name) + return i; + } + NPNR_ASSERT_FALSE("port user not found on net"); + }; + std::unordered_set used_ports; + + for (auto crit_net : crit_nets) { + + if (used_ports.count(&(crit_net.first->users.at(crit_net.second)))) + continue; + + std::deque crit_path; + + // FIXME: This will fail badly on combinational loops + + // Iterate backwards following greatest criticality + NetInfo *back_cursor = crit_net.first; + while (back_cursor != nullptr) { + float max_crit = 0; + std::pair crit_sink{nullptr, 0}; + CellInfo *cell = back_cursor->driver.cell; + if (cell == nullptr) + break; + for (auto port : cell->ports) { + if (port.second.type != PORT_IN) + continue; + NetInfo *pn = port.second.net; + if (pn == nullptr) + continue; + if (!net_crit.count(pn->name) || net_crit.at(pn->name).criticality.empty()) + continue; + int ccount; + DelayInfo combDelay; + TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount); + if (tpclass != TMG_COMB_INPUT) + continue; + bool is_path = ctx->getCellDelay(cell, port.first, back_cursor->driver.port, combDelay); + if (!is_path) + continue; + size_t user_idx = port_user_index(cell, port.second); + float usr_crit = net_crit.at(pn->name).criticality.at(user_idx); + if (used_ports.count(&(pn->users.at(user_idx)))) + continue; + if (usr_crit >= max_crit) { + max_crit = usr_crit; + crit_sink = std::make_pair(pn, user_idx); + } + } + + if (crit_sink.first != nullptr) { + crit_path.push_front(&(crit_sink.first->users.at(crit_sink.second))); + used_ports.insert(&(crit_sink.first->users.at(crit_sink.second))); + } + back_cursor = crit_sink.first; + } + // Iterate forwards following greatest criticiality + PortRef *fwd_cursor = &(crit_net.first->users.at(crit_net.second)); + while (fwd_cursor != nullptr) { + crit_path.push_back(fwd_cursor); + float max_crit = 0; + std::pair crit_sink{nullptr, 0}; + CellInfo *cell = fwd_cursor->cell; + for (auto port : cell->ports) { + if (port.second.type != PORT_OUT) + continue; + NetInfo *pn = port.second.net; + if (pn == nullptr) + continue; + if (!net_crit.count(pn->name) || net_crit.at(pn->name).criticality.empty()) + continue; + int ccount; + DelayInfo combDelay; + TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount); + if (tpclass != TMG_COMB_OUTPUT && tpclass != TMG_REGISTER_OUTPUT) + continue; + bool is_path = ctx->getCellDelay(cell, fwd_cursor->port, port.first, combDelay); + if (!is_path) + continue; + auto &crits = net_crit.at(pn->name).criticality; + for (size_t i = 0; i < crits.size(); i++) { + if (used_ports.count(&(pn->users.at(i)))) + continue; + if (crits.at(i) >= max_crit) { + max_crit = crits.at(i); + crit_sink = std::make_pair(pn, i); + } + } + } + if (crit_sink.first != nullptr) { + fwd_cursor = &(crit_sink.first->users.at(crit_sink.second)); + used_ports.insert(&(crit_sink.first->users.at(crit_sink.second))); + } else { + fwd_cursor = nullptr; + } + } + + std::vector crit_path_vec; + std::copy(crit_path.begin(), crit_path.end(), std::back_inserter(crit_path_vec)); + crit_paths.push_back(crit_path_vec); + } + + return crit_paths; + } + + void optimise_path(std::vector &path) + { + path_cells.clear(); + cell_neighbour_bels.clear(); + bel_candidate_cells.clear(); + if (ctx->debug) + log_info("Optimising the following path: \n"); + + auto front_port = path.front(); + NetInfo *front_net = front_port->cell->ports.at(front_port->port).net; + if (front_net != nullptr && front_net->driver.cell != nullptr) { + auto front_cell = front_net->driver.cell; + if (front_cell->belStrength <= STRENGTH_WEAK && cfg.cellTypes.count(front_cell->type) && + front_cell->constr_parent == nullptr && front_cell->constr_children.empty()) { + path_cells.push_back(front_cell->name); + } + } + + for (auto port : path) { + if (ctx->debug) { + float crit = 0; + NetInfo *pn = port->cell->ports.at(port->port).net; + if (net_crit.count(pn->name) && !net_crit.at(pn->name).criticality.empty()) + for (size_t i = 0; i < pn->users.size(); i++) + if (pn->users.at(i).cell == port->cell && pn->users.at(i).port == port->port) + crit = net_crit.at(pn->name).criticality.at(i); + log_info(" %s.%s at %s crit %0.02f\n", port->cell->name.c_str(ctx), port->port.c_str(ctx), + ctx->getBelName(port->cell->bel).c_str(ctx), crit); + } + if (std::find(path_cells.begin(), path_cells.end(), port->cell->name) != path_cells.end()) + continue; + if (port->cell->belStrength > STRENGTH_WEAK || !cfg.cellTypes.count(port->cell->type) || + port->cell->constr_parent != nullptr || !port->cell->constr_children.empty()) + continue; + if (ctx->debug) + log_info(" can move\n"); + path_cells.push_back(port->cell->name); + } + + if (path_cells.size() < 2) { + if (ctx->debug) { + log_info("Too few moveable cells; skipping path\n"); + log_break(); + } + + return; + } + + // Calculate original delay before touching anything + delay_t original_delay = 0; + + for (size_t i = 0; i < path.size(); i++) { + NetInfo *pn = path.at(i)->cell->ports.at(path.at(i)->port).net; + for (size_t j = 0; j < pn->users.size(); j++) { + auto &usr = pn->users.at(j); + if (usr.cell == path.at(i)->cell && usr.port == path.at(i)->port) { + original_delay += ctx->predictDelay(pn, usr); + break; + } + } + } + + IdString last_cell; + const int d = 2; // FIXME: how to best determine d + for (auto cell : path_cells) { + // FIXME: when should we allow swapping due to a lack of candidates + find_neighbours(ctx->cells[cell].get(), last_cell, d, false); + last_cell = cell; + } + + if (ctx->debug) { + for (auto cell : path_cells) { + log_info("Candidate neighbours for %s (%s):\n", cell.c_str(ctx), + ctx->getBelName(ctx->cells[cell]->bel).c_str(ctx)); + for (auto neigh : cell_neighbour_bels.at(cell)) { + log_info(" %s\n", ctx->getBelName(neigh).c_str(ctx)); + } + } + } + + // Actual BFS path optimisation algorithm + std::unordered_map> cumul_costs; + std::unordered_map, std::pair> backtrace; + std::queue> visit; + std::unordered_set> to_visit; + + for (auto startbel : cell_neighbour_bels[path_cells.front()]) { + // Swap for legality check + CellInfo *cell = ctx->cells.at(path_cells.front()).get(); + BelId origBel = cell_swap_bel(cell, startbel); + std::vector> move{std::make_pair(cell, origBel)}; + if (acceptable_move(move)) { + auto entry = std::make_pair(0, startbel); + visit.push(entry); + cumul_costs[path_cells.front()][startbel] = 0; + } + // Swap back + cell_swap_bel(cell, origBel); + } + + while (!visit.empty()) { + auto entry = visit.front(); + visit.pop(); + auto cellname = path_cells.at(entry.first); + if (entry.first == int(path_cells.size()) - 1) + continue; + std::vector> move; + // Apply the entire backtrace for accurate legality and delay checks + // This is probably pretty expensive (but also probably pales in comparison to the number of swaps + // SA will make...) + std::vector> route_to_entry; + auto cursor = std::make_pair(cellname, entry.second); + route_to_entry.push_back(cursor); + while (backtrace.count(cursor)) { + cursor = backtrace.at(cursor); + route_to_entry.push_back(cursor); + } + for (auto rt_entry : boost::adaptors::reverse(route_to_entry)) { + CellInfo *cell = ctx->cells.at(rt_entry.first).get(); + BelId origBel = cell_swap_bel(cell, rt_entry.second); + move.push_back(std::make_pair(cell, origBel)); + } + + // Have a look at where we can travel from here + for (auto neighbour : cell_neighbour_bels.at(path_cells.at(entry.first + 1))) { + // Edges between overlapping bels are deleted + if (neighbour == entry.second) + continue; + // Experimentally swap the next path cell onto the neighbour bel we are trying + IdString ncname = path_cells.at(entry.first + 1); + CellInfo *next_cell = ctx->cells.at(ncname).get(); + BelId origBel = cell_swap_bel(next_cell, neighbour); + move.push_back(std::make_pair(next_cell, origBel)); + + delay_t total_delay = 0; + + for (size_t i = 0; i < path.size(); i++) { + NetInfo *pn = path.at(i)->cell->ports.at(path.at(i)->port).net; + for (size_t j = 0; j < pn->users.size(); j++) { + auto &usr = pn->users.at(j); + if (usr.cell == path.at(i)->cell && usr.port == path.at(i)->port) { + total_delay += ctx->predictDelay(pn, usr); + break; + } + } + if (path.at(i)->cell == next_cell) + break; + } + + // First, check if the move is actually worthwhile from a delay point of view before the expensive + // legality check + if (!cumul_costs.count(ncname) || !cumul_costs.at(ncname).count(neighbour) || + cumul_costs.at(ncname).at(neighbour) > total_delay) { + // Now check that the swaps we have made to get here are legal and meet max delay requirements + if (acceptable_move(move)) { + cumul_costs[ncname][neighbour] = total_delay; + backtrace[std::make_pair(ncname, neighbour)] = std::make_pair(cellname, entry.second); + if (!to_visit.count(std::make_pair(entry.first + 1, neighbour))) + visit.push(std::make_pair(entry.first + 1, neighbour)); + } + } + // Revert the experimental swap + cell_swap_bel(move.back().first, move.back().second); + move.pop_back(); + } + + // Revert move by swapping cells back to their original order + // Execute swaps in reverse order to how we made them originally + for (auto move_entry : boost::adaptors::reverse(move)) { + cell_swap_bel(move_entry.first, move_entry.second); + } + } + + // Did we find a solution?? + if (cumul_costs.count(path_cells.back())) { + // Find the end position with the lowest total delay + auto &end_options = cumul_costs.at(path_cells.back()); + auto lowest = std::min_element(end_options.begin(), end_options.end(), + [](const std::pair &a, const std::pair &b) { + return a.second < b.second; + }); + NPNR_ASSERT(lowest != end_options.end()); + + std::vector> route_to_solution; + auto cursor = std::make_pair(path_cells.back(), lowest->first); + route_to_solution.push_back(cursor); + while (backtrace.count(cursor)) { + cursor = backtrace.at(cursor); + route_to_solution.push_back(cursor); + } + if (ctx->debug) + log_info("Found a solution with cost %.02f ns (existing path %.02f ns)\n", + ctx->getDelayNS(lowest->second), ctx->getDelayNS(original_delay)); + for (auto rt_entry : boost::adaptors::reverse(route_to_solution)) { + CellInfo *cell = ctx->cells.at(rt_entry.first).get(); + cell_swap_bel(cell, rt_entry.second); + if (ctx->debug) + log_info(" %s at %s\n", rt_entry.first.c_str(ctx), ctx->getBelName(rt_entry.second).c_str(ctx)); + } + + } else { + if (ctx->debug) + log_info("Solution was not found\n"); + } + if (ctx->debug) + log_break(); + } + + // Current candidate Bels for cells (linked in both direction> + std::vector path_cells; + std::unordered_map> cell_neighbour_bels; + std::unordered_map> bel_candidate_cells; + // Map cell ports to net delay limit + std::unordered_map, delay_t> max_net_delay; + // Criticality data from timing analysis + NetCriticalityMap net_crit; + Context *ctx; + TimingOptCfg cfg; +}; + +bool timing_opt(Context *ctx, TimingOptCfg cfg) { return TimingOptimiser(ctx, cfg).optimise(); } + +NEXTPNR_NAMESPACE_END diff --git a/common/timing_opt.h b/common/timing_opt.h new file mode 100644 index 00000000..ceb35c71 --- /dev/null +++ b/common/timing_opt.h @@ -0,0 +1,37 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "nextpnr.h" +#include "settings.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct TimingOptCfg : public Settings +{ + TimingOptCfg(Context *ctx) : Settings(ctx) {} + + // The timing optimiser will *only* optimise cells of these types + // Normally these would only be logic cells (or tiles if applicable), the algorithm makes little sense + // for other cell types + std::unordered_set cellTypes; +}; + +extern bool timing_opt(Context *ctx, TimingOptCfg cfg); + +NEXTPNR_NAMESPACE_END diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 22b350c7..719426ab 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -670,7 +670,8 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in } return TMG_IGNORE; } else { - NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'"); + log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this), + cell->name.c_str(this)); } } diff --git a/ecp5/globals.cc b/ecp5/globals.cc index 66c62024..ddaae5e5 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -298,6 +298,8 @@ class Ecp5GlobalRouter } else { // Check for dedicated routing if (has_short_route(ctx->getBelPinWire(drv_bel, drv.port), ctx->getBelPinWire(dcc->bel, id_CLKI))) { + // log_info("dedicated route %s -> %s\n", ctx->getWireName(ctx->getBelPinWire(drv_bel, + // drv.port)).c_str(ctx), ctx->getBelName(dcc->bel).c_str(ctx)); return 0; } // Driver is locked @@ -308,7 +310,7 @@ class Ecp5GlobalRouter } // Return true if a short (<5) route exists between two wires - bool has_short_route(WireId src, WireId dst, int thresh = 5) + bool has_short_route(WireId src, WireId dst, int thresh = 7) { std::queue visit; std::unordered_map backtrace; @@ -316,7 +318,7 @@ class Ecp5GlobalRouter WireId cursor; while (true) { - if (visit.empty() || visit.size() > 1000) { + if (visit.empty() || visit.size() > 10000) { // log_info ("dist %s -> %s = inf\n", ctx->getWireName(src).c_str(ctx), // ctx->getWireName(dst).c_str(ctx)); return false; diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 78bf7a87..ca329530 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -1323,6 +1323,60 @@ class Ecp5Packer } } + // Preplace PLL + void preplace_plls() + { + std::set available_plls; + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) == id_EHXPLLL && ctx->checkBelAvail(bel)) + available_plls.insert(bel); + } + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_EHXPLLL && ci->attrs.count(ctx->id("BEL"))) + available_plls.erase(ctx->getBelByName(ctx->id(ci->attrs.at(ctx->id("BEL"))))); + } + // Place PLL connected to fixed drivers such as IO close to their source + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_EHXPLLL && !ci->attrs.count(ctx->id("BEL"))) { + const NetInfo *drivernet = net_or_nullptr(ci, id_CLKI); + if (drivernet == nullptr || drivernet->driver.cell == nullptr) + continue; + const CellInfo *drivercell = drivernet->driver.cell; + if (!drivercell->attrs.count(ctx->id("BEL"))) + continue; + BelId drvbel = ctx->getBelByName(ctx->id(drivercell->attrs.at(ctx->id("BEL")))); + Loc drvloc = ctx->getBelLocation(drvbel); + BelId closest_pll; + int closest_distance = std::numeric_limits::max(); + for (auto bel : available_plls) { + Loc pllloc = ctx->getBelLocation(bel); + int distance = std::abs(drvloc.x - pllloc.x) + std::abs(drvloc.y - pllloc.y); + if (distance < closest_distance) { + closest_pll = bel; + closest_distance = distance; + } + } + if (closest_pll == BelId()) + log_error("failed to place PLL '%s'\n", ci->name.c_str(ctx)); + available_plls.erase(closest_pll); + ci->attrs[ctx->id("BEL")] = ctx->getBelName(closest_pll).str(ctx); + } + } + // Place PLLs driven by logic, etc, randomly + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_EHXPLLL && !ci->attrs.count(ctx->id("BEL"))) { + if (available_plls.empty()) + log_error("failed to place PLL '%s'\n", ci->name.c_str(ctx)); + BelId next_pll = *(available_plls.begin()); + available_plls.erase(next_pll); + ci->attrs[ctx->id("BEL")] = ctx->getBelName(next_pll).str(ctx); + } + } + } + public: void pack() { @@ -1330,6 +1384,7 @@ class Ecp5Packer pack_ebr(); pack_dsps(); pack_dcus(); + preplace_plls(); pack_constants(); pack_dram(); pack_carries(); diff --git a/gui/application.cc b/gui/application.cc index ccbf5645..738b7c68 100644 --- a/gui/application.cc +++ b/gui/application.cc @@ -41,6 +41,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) { QSurfaceFormat fmt; fmt.setSamples(10); + fmt.setProfile(QSurfaceFormat::CoreProfile); QSurfaceFormat::setDefaultFormat(fmt); #ifdef _WIN32 SetConsoleCtrlHandler((PHANDLER_ROUTINE)WinHandler, TRUE); diff --git a/gui/worker.cc b/gui/worker.cc index b009ecd3..900883d4 100644 --- a/gui/worker.cc +++ b/gui/worker.cc @@ -126,6 +126,7 @@ TaskManager::TaskManager() : toTerminate(false), toPause(false) TaskManager::~TaskManager() { + log_write_function = nullptr; if (workerThread.isRunning()) terminate_thread(); workerThread.quit(); diff --git a/ice40/arch.cc b/ice40/arch.cc index 02e5515b..8f52987c 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -26,6 +26,7 @@ #include "nextpnr.h" #include "placer1.h" #include "router1.h" +#include "timing_opt.h" #include "util.h" NEXTPNR_NAMESPACE_BEGIN @@ -626,7 +627,18 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay // ----------------------------------------------------------------------- -bool Arch::place() { return placer1(getCtx(), Placer1Cfg(getCtx())); } +bool Arch::place() +{ + if (!placer1(getCtx(), Placer1Cfg(getCtx()))) + return false; + if (bool_or_default(settings, id("opt_timing"), false)) { + TimingOptCfg tocfg(getCtx()); + tocfg.cellTypes.insert(id_ICESTORM_LC); + return timing_opt(getCtx(), tocfg); + } else { + return true; + } +} bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); } @@ -950,8 +962,12 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in if (port == id_RGB0 || port == id_RGB1 || port == id_RGB2) return TMG_IGNORE; return TMG_ENDPOINT; + } else if (cell->type == id_SB_LEDDA_IP) { + if (port == id_CLK || port == id_CLOCK) + return TMG_CLOCK_INPUT; + return TMG_IGNORE; } - log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this)); + log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this), cell->name.c_str(this)); } TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index ecb26753..d1f51676 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -156,7 +156,9 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce // Lattice's weird string style params, not sure if // prefixes other than 0b should be supported, only 0b features in docs std::string raw = get_param_str_or_def(cell, ctx->id(p.first), "0b0"); - assert(raw.substr(0, 2) == "0b"); + if (raw.substr(0, 2) != "0b") + log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n", + p.first.c_str(), cell->name.c_str(ctx)); raw = raw.substr(2); value.resize(raw.length()); for (int i = 0; i < (int)raw.length(); i++) { @@ -168,7 +170,13 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce } } } else { - int ival = get_param_or_def(cell, ctx->id(p.first), 0); + int ival; + try { + ival = get_param_or_def(cell, ctx->id(p.first), 0); + } catch (std::invalid_argument &e) { + log_error("expected numeric value for parameter '%s' on cell '%s'\n", p.first.c_str(), + cell->name.c_str(ctx)); + } for (int i = 0; i < p.second; i++) value.push_back((ival >> i) & 0x1); @@ -486,7 +494,7 @@ void write_asc(const Context *ctx, std::ostream &out) unsigned pin_type = get_param_or_def(cell.second.get(), ctx->id("PIN_TYPE")); bool neg_trigger = get_param_or_def(cell.second.get(), ctx->id("NEG_TRIGGER")); bool pullup = get_param_or_def(cell.second.get(), ctx->id("PULLUP")); - bool lvds = get_param_str_or_def(cell.second.get(), ctx->id("IO_STANDARD")) == "SB_LVDS_INPUT"; + bool lvds = cell.second->ioInfo.lvds; bool used_by_pll_out = sb_io_used_by_pll_out.count(Loc(x, y, z)) > 0; bool used_by_pll_pad = sb_io_used_by_pll_pad.count(Loc(x, y, z)) > 0; @@ -495,64 +503,64 @@ void write_asc(const Context *ctx, std::ostream &out) set_config(ti, config.at(y).at(x), "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i), val); } set_config(ti, config.at(y).at(x), "NegClk", neg_trigger); - auto ieren = get_ieren(bi, x, y, z); - int iex, iey, iez; - std::tie(iex, iey, iez) = ieren; - NPNR_ASSERT(iez != -1); - bool input_en; - if (lvds) { - input_en = false; - pullup = false; - } else { - if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) || - (ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) { - input_en = true; - } else { - input_en = false; - } + bool input_en = false; + if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) || + (ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) { + input_en = true; } - input_en = (input_en & !used_by_pll_out) | used_by_pll_pad; input_en |= cell.second->ioInfo.global; - if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { - set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), !input_en); - set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); - } else { - set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), input_en); - set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); - } - - if (ctx->args.type == ArchArgs::UP5K) { - if (iez == 0) { - set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup); - } else if (iez == 1) { - set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup); - } - } - - if (lvds) { - NPNR_ASSERT(z == 0); - set_config(ti, config.at(y).at(x), "IoCtrl.LVDS", true); - // Set comp IO config - auto comp_ieren = get_ieren(bi, x, y, 1); - int ciex, ciey, ciez; - std::tie(ciex, ciey, ciez) = comp_ieren; + if (!lvds) { + auto ieren = get_ieren(bi, x, y, z); + int iex, iey, iez; + std::tie(iex, iey, iez) = ieren; + NPNR_ASSERT(iez != -1); if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), !input_en); - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup); + set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), !input_en); + set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); } else { - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), input_en); - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup); + set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), input_en); + set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); } if (ctx->args.type == ArchArgs::UP5K) { - if (ciez == 0) { - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_39", !pullup); + if (iez == 0) { + set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup); } else if (iez == 1) { - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_35", !pullup); + set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup); + } + } + } else { + NPNR_ASSERT(z == 0); + // Only enable the actual LVDS buffer if input is used for something + set_config(ti, config.at(y).at(x), "IoCtrl.LVDS", input_en); + + // Set both IO config + for (int cz = 0; cz < 2; cz++) { + auto ieren = get_ieren(bi, x, y, cz); + int iex, iey, iez; + std::tie(iex, iey, iez) = ieren; + NPNR_ASSERT(iez != -1); + + pullup &= !input_en; /* If input is used, force disable pullups */ + + if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { + set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true); + set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); + } else { + set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), false); + set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); + } + + if (ctx->args.type == ArchArgs::UP5K) { + if (iez == 0) { + set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup); + } else if (iez == 1) { + set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup); + } } } } @@ -591,7 +599,8 @@ void write_asc(const Context *ctx, std::ostream &out) {"CURRENT_MODE", 1}, {"RGB0_CURRENT", 6}, {"RGB1_CURRENT", 6}, {"RGB2_CURRENT", 6}}; configure_extra_cell(config, ctx, cell.second.get(), rgba_params, true, std::string("IpConfig.")); set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "RGBA_DRV_EN", true, "IpConfig."); - } else if (cell.second->type == ctx->id("SB_WARMBOOT") || cell.second->type == ctx->id("ICESTORM_LFOSC")) { + } else if (cell.second->type == ctx->id("SB_WARMBOOT") || cell.second->type == ctx->id("ICESTORM_LFOSC") || + cell.second->type == ctx->id("SB_LEDDA_IP")) { // No config needed } else if (cell.second->type == ctx->id("ICESTORM_SPRAM")) { const BelInfoPOD &beli = ci.bel_data[bel.index]; @@ -688,7 +697,7 @@ void write_asc(const Context *ctx, std::ostream &out) int iex, iey, iez; std::tie(iex, iey, iez) = ieren; if (iez != -1) { - // IO is not actually unused if part of an LVDS pair + // If IO is in LVDS pair, it will be configured by the other pair if (z == 1) { BelId lvds0 = ctx->getBelByLocation(Loc{x, y, 0}); const CellInfo *lvds0cell = ctx->getBoundBelCell(lvds0); diff --git a/ice40/cells.cc b/ice40/cells.cc index dbb75c2c..35a5346f 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -260,6 +260,21 @@ std::unique_ptr create_ice_cell(Context *ctx, IdString type, std::stri add_port(ctx, new_cell.get(), "RGB0", PORT_OUT); add_port(ctx, new_cell.get(), "RGB1", PORT_OUT); add_port(ctx, new_cell.get(), "RGB2", PORT_OUT); + } else if (type == ctx->id("SB_LEDDA_IP")) { + add_port(ctx, new_cell.get(), "LEDDCS", PORT_IN); + add_port(ctx, new_cell.get(), "LEDDCLK", PORT_IN); + for (int i = 0; i < 8; i++) + add_port(ctx, new_cell.get(), "LEDDDAT" + std::to_string(i), PORT_IN); + for (int i = 0; i < 3; i++) + add_port(ctx, new_cell.get(), "LEDDADDR" + std::to_string(i), PORT_IN); + add_port(ctx, new_cell.get(), "LEDDDEN", PORT_IN); + add_port(ctx, new_cell.get(), "LEDDEXE", PORT_IN); + add_port(ctx, new_cell.get(), "LEDDRST", PORT_IN); // doesn't actually exist, for icecube code compatibility + // only + add_port(ctx, new_cell.get(), "PWMOUT0", PORT_OUT); + add_port(ctx, new_cell.get(), "PWMOUT1", PORT_OUT); + add_port(ctx, new_cell.get(), "PWMOUT2", PORT_OUT); + add_port(ctx, new_cell.get(), "LEDDON", PORT_OUT); } else { log_error("unable to create iCE40 cell of type %s", type.c_str(ctx)); } diff --git a/ice40/cells.h b/ice40/cells.h index 1fbd9073..93ef3db4 100644 --- a/ice40/cells.h +++ b/ice40/cells.h @@ -76,6 +76,8 @@ inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell- inline bool is_sb_rgba_drv(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_RGBA_DRV"); } +inline bool is_sb_ledda_ip(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_LEDDA_IP"); } + inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") || diff --git a/ice40/chains.cc b/ice40/chains.cc index fb361d2d..b3b54d6b 100644 --- a/ice40/chains.cc +++ b/ice40/chains.cc @@ -62,7 +62,7 @@ class ChainConstrainer bool split_chain = (!ctx->logicCellsCompatible(tile.data(), tile.size())) || (int(chains.back().cells.size()) > max_length); if (split_chain) { - CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT"))); + CellInfo *passout = make_carry_pass_out((*(curr_cell - 1))->ports.at(ctx->id("COUT"))); tile.pop_back(); chains.back().cells.back() = passout; start_of_chain = true; @@ -74,10 +74,10 @@ class ChainConstrainer (net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), false) != net_only_drives(ctx, carry_net, is_lc, ctx->id("CIN"), false)) || (at_end && !net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), true))) { - CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT"))); + CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")), + at_end ? nullptr : *(curr_cell + 1)); chains.back().cells.push_back(passout); tile.push_back(passout); - start_of_chain = true; } } ++curr_cell; @@ -87,30 +87,75 @@ class ChainConstrainer } // Insert a logic cell to legalise a COUT->fabric connection - CellInfo *make_carry_pass_out(PortInfo &cout_port) + CellInfo *make_carry_pass_out(PortInfo &cout_port, CellInfo *cin_cell = nullptr) { NPNR_ASSERT(cout_port.net != nullptr); std::unique_ptr lc = create_ice_cell(ctx, ctx->id("ICESTORM_LC")); lc->params[ctx->id("LUT_INIT")] = "65280"; // 0xff00: O = I3 lc->params[ctx->id("CARRY_ENABLE")] = "1"; - lc->ports.at(ctx->id("O")).net = cout_port.net; + lc->ports.at(id_O).net = cout_port.net; std::unique_ptr co_i3_net(new NetInfo()); co_i3_net->name = ctx->id(lc->name.str(ctx) + "$I3"); co_i3_net->driver = cout_port.net->driver; PortRef i3_r; - i3_r.port = ctx->id("I3"); + i3_r.port = id_I3; i3_r.cell = lc.get(); co_i3_net->users.push_back(i3_r); PortRef o_r; - o_r.port = ctx->id("O"); + o_r.port = id_O; o_r.cell = lc.get(); cout_port.net->driver = o_r; - lc->ports.at(ctx->id("I3")).net = co_i3_net.get(); + lc->ports.at(id_I3).net = co_i3_net.get(); cout_port.net = co_i3_net.get(); IdString co_i3_name = co_i3_net->name; NPNR_ASSERT(ctx->nets.find(co_i3_name) == ctx->nets.end()); ctx->nets[co_i3_name] = std::move(co_i3_net); + + // If COUT also connects to a CIN; preserve the carry chain + if (cin_cell) { + std::unique_ptr co_cin_net(new NetInfo()); + co_cin_net->name = ctx->id(lc->name.str(ctx) + "$COUT"); + + // Connect I1 to 1 to preserve carry chain + NetInfo *vcc = ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get(); + lc->ports.at(id_I1).net = vcc; + PortRef i1_r; + i1_r.port = id_I1; + i1_r.cell = lc.get(); + vcc->users.push_back(i1_r); + + // Connect co_cin_net to the COUT of the LC + PortRef co_r; + co_r.port = id_COUT; + co_r.cell = lc.get(); + co_cin_net->driver = co_r; + lc->ports.at(id_COUT).net = co_cin_net.get(); + + // Find the user corresponding to the next CIN + int replaced_ports = 0; + if (ctx->debug) + log_info("cell: %s\n", cin_cell->name.c_str(ctx)); + for (auto port : {id_CIN, id_I3}) { + auto &usr = lc->ports.at(id_O).net->users; + if (ctx->debug) + for (auto user : usr) + log_info("%s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx)); + auto fnd_user = std::find_if(usr.begin(), usr.end(), + [&](const PortRef &pr) { return pr.cell == cin_cell && pr.port == port; }); + if (fnd_user != usr.end()) { + co_cin_net->users.push_back(*fnd_user); + usr.erase(fnd_user); + cin_cell->ports.at(port).net = co_cin_net.get(); + ++replaced_ports; + } + } + NPNR_ASSERT(replaced_ports > 0); + IdString co_cin_name = co_cin_net->name; + NPNR_ASSERT(ctx->nets.find(co_cin_name) == ctx->nets.end()); + ctx->nets[co_cin_name] = std::move(co_cin_net); + } + IdString name = lc->name; ctx->assignCellInfo(lc.get()); ctx->cells[lc->name] = std::move(lc); @@ -163,29 +208,31 @@ class ChainConstrainer void process_carries() { - std::vector carry_chains = - find_chains(ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); }, - [](const Context *ctx, const + std::vector carry_chains = find_chains( + ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); }, + [](const Context *ctx, const - CellInfo *cell) { - CellInfo *carry_prev = - net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT")); - if (carry_prev != nullptr) - return carry_prev; - /*CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc, - ctx->id("COUT")); if (i3_prev != nullptr) return i3_prev;*/ - return (CellInfo *)nullptr; - }, - [](const Context *ctx, const CellInfo *cell) { - CellInfo *carry_next = net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, - ctx->id("CIN"), false); - if (carry_next != nullptr) - return carry_next; - /*CellInfo *i3_next = - net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"), - false); if (i3_next != nullptr) return i3_next;*/ - return (CellInfo *)nullptr; - }); + CellInfo *cell) { + CellInfo *carry_prev = + net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT")); + if (carry_prev != nullptr) + return carry_prev; + CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc, ctx->id("COUT")); + if (i3_prev != nullptr) + return i3_prev; + return (CellInfo *)nullptr; + }, + [](const Context *ctx, const CellInfo *cell) { + CellInfo *carry_next = + net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("CIN"), false); + if (carry_next != nullptr) + return carry_next; + CellInfo *i3_next = + net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"), false); + if (i3_next != nullptr) + return i3_next; + return (CellInfo *)nullptr; + }); std::unordered_set chained; for (auto &base_chain : carry_chains) { for (auto c : base_chain.cells) diff --git a/ice40/main.cc b/ice40/main.cc index fcc56d04..543bd229 100644 --- a/ice40/main.cc +++ b/ice40/main.cc @@ -65,6 +65,10 @@ po::options_description Ice40CommandHandler::getArchOptions() specific.add_options()("pcf", po::value(), "PCF constraints file to ingest"); specific.add_options()("asc", po::value(), "asc bitstream file to write"); specific.add_options()("read", po::value(), "asc bitstream file to read"); + specific.add_options()("promote-logic", + "enable promotion of 'logic' globals (in addition to clk/ce/sr by default)"); + specific.add_options()("no-promote-globals", "disable all global promotion"); + specific.add_options()("opt-timing", "run post-placement timing optimisation pass (experimental)"); specific.add_options()("tmfuzz", "run path delay estimate fuzzer"); return specific; } @@ -152,7 +156,15 @@ std::unique_ptr Ice40CommandHandler::createContext() if (vm.count("package")) chipArgs.package = vm["package"].as(); - return std::unique_ptr(new Context(chipArgs)); + auto ctx = std::unique_ptr(new Context(chipArgs)); + + if (vm.count("promote-logic")) + ctx->settings[ctx->id("promote_logic")] = "1"; + if (vm.count("no-promote-globals")) + ctx->settings[ctx->id("no_promote_globals")] = "1"; + if (vm.count("opt-timing")) + ctx->settings[ctx->id("opt_timing")] = "1"; + return ctx; } int main(int argc, char *argv[]) diff --git a/ice40/pack.cc b/ice40/pack.cc index fc28121e..242f8ceb 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -478,6 +478,9 @@ static void pack_io(Context *ctx) } packed_cells.insert(ci->name); std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(sb->attrs, sb->attrs.begin())); + if (!sb->attrs.count(ctx->id("BEL"))) + log_warning("IO '%s' is not constrained to a pin and will be automatically placed\n", + ci->name.c_str(ctx)); } else if (is_sb_io(ctx, ci) || is_sb_gb_io(ctx, ci)) { NetInfo *net = ci->ports.at(ctx->id("PACKAGE_PIN")).net; if ((net != nullptr) && (net->users.size() > 1)) @@ -518,10 +521,10 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port) !is_sb_pll40(ctx, port.cell); } -static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic) +static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic, int fanout) { - log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "", - is_logic ? " [logic]" : ""); + log_info("promoting %s%s%s%s (fanout %d)\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", + is_cen ? " [cen]" : "", is_logic ? " [logic]" : "", fanout); std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk")); std::unique_ptr gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name); @@ -565,7 +568,8 @@ static void promote_globals(Context *ctx) { log_info("Promoting globals..\n"); const int logic_fanout_thresh = 15; - const int enable_fanout_thresh = 5; + const int enable_fanout_thresh = 15; + const int reset_fanout_thresh = 15; std::map clock_count, reset_count, cen_count, logic_count; for (auto net : sorted(ctx->nets)) { NetInfo *ni = net.second; @@ -637,18 +641,20 @@ static void promote_globals(Context *ctx) }); if (global_clock->second == 0 && prom_logics < 4 && global_logic->second > logic_fanout_thresh && (global_logic->second > global_cen->second || prom_cens >= cens_available) && - (global_logic->second > global_reset->second || prom_resets >= resets_available)) { + (global_logic->second > global_reset->second || prom_resets >= resets_available) && + bool_or_default(ctx->settings, ctx->id("promote_logic"), false)) { NetInfo *logicnet = ctx->nets[global_logic->first].get(); - insert_global(ctx, logicnet, false, false, true); + insert_global(ctx, logicnet, false, false, true, global_logic->second); ++prom_globals; ++prom_logics; clock_count.erase(logicnet->name); 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 < resets_available) { + } else if (global_reset->second > global_clock->second && prom_resets < resets_available && + global_reset->second > reset_fanout_thresh) { NetInfo *rstnet = ctx->nets[global_reset->first].get(); - insert_global(ctx, rstnet, true, false, false); + insert_global(ctx, rstnet, true, false, false, global_reset->second); ++prom_globals; ++prom_resets; clock_count.erase(rstnet->name); @@ -658,7 +664,7 @@ static void promote_globals(Context *ctx) } 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); + insert_global(ctx, cennet, false, true, false, global_cen->second); ++prom_globals; ++prom_cens; clock_count.erase(cennet->name); @@ -667,7 +673,7 @@ static void promote_globals(Context *ctx) logic_count.erase(cennet->name); } else if (global_clock->second != 0) { NetInfo *clknet = ctx->nets[global_clock->first].get(); - insert_global(ctx, clknet, false, false, false); + insert_global(ctx, clknet, false, false, false, global_clock->second); ++prom_globals; clock_count.erase(clknet->name); reset_count.erase(clknet->name); @@ -679,6 +685,202 @@ static void promote_globals(Context *ctx) } } +// Figure out where to place PLLs +static void place_plls(Context *ctx) +{ + std::map> pll_all_bels; + std::map pll_used_bels; + std::vector pll_cells; + std::map bel2io; + + log_info("Placing PLLs..\n"); + + // Find all the PLLs BELs and matching IO sites + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) != id_ICESTORM_PLL) + continue; + if (ctx->isBelLocked(bel)) + continue; + + auto io_a_pin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_A); + auto io_b_pin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_B); + + pll_all_bels[bel] = std::make_pair(io_a_pin, io_b_pin); + } + + // Find all the PLLs cells we need to place and do pre-checks + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (!is_sb_pll40(ctx, ci)) + continue; + + // If it's constrained already, add to already used list + if (ci->attrs.count(ctx->id("BEL"))) { + BelId bel_constrain = ctx->getBelByName(ctx->id(ci->attrs[ctx->id("BEL")])); + if (pll_all_bels.count(bel_constrain) == 0) + log_error("PLL '%s' is constrained to invalid BEL '%s'\n", ci->name.c_str(ctx), + ci->attrs[ctx->id("BEL")].c_str()); + pll_used_bels[bel_constrain] = ci; + } + + // Add it to our list of PLLs to process + pll_cells.push_back(ci); + } + + // Scan all the PAD PLLs + for (auto ci : pll_cells) { + if (!is_sb_pll40_pad(ctx, ci)) + continue; + + // Check PACKAGEPIN connection + if (!ci->ports.count(ctx->id("PACKAGEPIN"))) + log_error("PLL '%s' is of PAD type but doesn't have a PACKAGEPIN port\n", ci->name.c_str(ctx)); + + NetInfo *ni = ci->ports.at(ctx->id("PACKAGEPIN")).net; + if (ni == nullptr || ni->driver.cell == nullptr) + log_error("PLL '%s' is of PAD type but doesn't have a valid PACKAGEPIN connection\n", ci->name.c_str(ctx)); + + CellInfo *io_cell = ni->driver.cell; + if (io_cell->type != id_SB_IO || ni->driver.port != id_D_IN_0) + log_error("PLL '%s' has a PACKAGEPIN driven by an %s, should be directly connected to an input " + "SB_IO.D_IN_0 port\n", + ci->name.c_str(ctx), io_cell->type.c_str(ctx)); + if (ni->users.size() != 1) + log_error("PLL '%s' clock input '%s' can only drive PLL\n", ci->name.c_str(ctx), ni->name.c_str(ctx)); + if (!io_cell->attrs.count(ctx->id("BEL"))) + log_error("PLL '%s' PACKAGEPIN SB_IO '%s' is unconstrained\n", ci->name.c_str(ctx), + io_cell->name.c_str(ctx)); + + BelId io_bel = ctx->getBelByName(ctx->id(io_cell->attrs.at(ctx->id("BEL")))); + BelId found_bel; + + // Find the PLL BEL that would suit that connection + for (auto pll_bel : pll_all_bels) { + if (std::get<0>(pll_bel.second).bel == io_bel) { + found_bel = pll_bel.first; + break; + } + } + + if (found_bel == BelId()) + log_error("PLL '%s' PACKAGEPIN SB_IO '%s' is not connected to any PLL BEL\n", ci->name.c_str(ctx), + io_cell->name.c_str(ctx)); + if (pll_used_bels.count(found_bel)) { + CellInfo *conflict_cell = pll_used_bels.at(found_bel); + log_error("PLL '%s' PACKAGEPIN forces it to BEL %s but BEL is already assigned to PLL '%s'\n", + ci->name.c_str(ctx), ctx->getBelName(found_bel).c_str(ctx), conflict_cell->name.c_str(ctx)); + } + + // Is it user constrained ? + if (ci->attrs.count(ctx->id("BEL"))) { + // Yes. Check it actually matches ! + BelId bel_constrain = ctx->getBelByName(ctx->id(ci->attrs[ctx->id("BEL")])); + if (bel_constrain != found_bel) + log_error("PLL '%s' is user constrained to %s but can only be placed in %s based on its PACKAGEPIN " + "connection\n", + ci->name.c_str(ctx), ctx->getBelName(bel_constrain).c_str(ctx), + ctx->getBelName(found_bel).c_str(ctx)); + } else { + // No, we can constrain it ourselves + ci->attrs[ctx->id("BEL")] = ctx->getBelName(found_bel).str(ctx); + pll_used_bels[found_bel] = ci; + } + + // Inform user + log_info(" constrained PLL '%s' to %s\n", ci->name.c_str(ctx), ctx->getBelName(found_bel).c_str(ctx)); + } + + // Scan all SB_IOs to check for conflict with PLL BELs + for (auto io_cell : sorted(ctx->cells)) { + CellInfo *io_ci = io_cell.second; + if (!is_sb_io(ctx, io_ci)) + continue; + + // Only consider bound IO that are used as inputs + if (!io_ci->attrs.count(ctx->id("BEL"))) + continue; + if ((!io_ci->ports.count(id_D_IN_0) || (io_ci->ports[id_D_IN_0].net == nullptr)) && + (!io_ci->ports.count(id_D_IN_1) || (io_ci->ports[id_D_IN_1].net == nullptr))) + continue; + + // Check all placed PLL (either forced by user, or forced by PACKAGEPIN) + BelId io_bel = ctx->getBelByName(ctx->id(io_ci->attrs[ctx->id("BEL")])); + + for (auto placed_pll : pll_used_bels) { + BelPin pll_io_a, pll_io_b; + std::tie(pll_io_a, pll_io_b) = pll_all_bels[placed_pll.first]; + if (io_bel == pll_io_a.bel) { + // All the PAD type PLL stuff already checked above,so only + // check for conflict with a user placed CORE PLL + if (!is_sb_pll40_pad(ctx, placed_pll.second)) + log_error("PLL '%s' A output conflict with SB_IO '%s' that's used as input\n", + placed_pll.second->name.c_str(ctx), io_cell.second->name.c_str(ctx)); + } else if (io_bel == pll_io_b.bel) { + if (is_sb_pll40_dual(ctx, placed_pll.second)) + log_error("PLL '%s' B output conflicts with SB_IO '%s' that's used as input\n", + placed_pll.second->name.c_str(ctx), io_cell.second->name.c_str(ctx)); + } + } + + // Save for later checks + bel2io[io_bel] = io_ci; + } + + // Scan all the CORE PLLs and place them in remaining available PLL BELs + // (in two pass ... first do the dual ones, harder to place, then single port) + for (int i = 0; i < 2; i++) { + for (auto ci : pll_cells) { + if (is_sb_pll40_pad(ctx, ci)) + continue; + if (is_sb_pll40_dual(ctx, ci) ^ i) + continue; + + // Check REFERENCECLK connection + if (!ci->ports.count(id_REFERENCECLK)) + log_error("PLL '%s' is of CORE type but doesn't have a REFERENCECLK port\n", ci->name.c_str(ctx)); + + NetInfo *ni = ci->ports.at(id_REFERENCECLK).net; + if (ni == nullptr || ni->driver.cell == nullptr) + log_error("PLL '%s' is of CORE type but doesn't have a valid REFERENCECLK connection\n", + ci->name.c_str(ctx)); + + // Could this be a PAD PLL ? + bool could_be_pad = false; + BelId pad_bel; + if (ni->users.size() == 1 && is_sb_io(ctx, ni->driver.cell) && ni->driver.cell->attrs.count(ctx->id("BEL"))) + pad_bel = ctx->getBelByName(ctx->id(ni->driver.cell->attrs[ctx->id("BEL")])); + + // Find a BEL for it + BelId found_bel; + for (auto bel_pll : pll_all_bels) { + BelPin pll_io_a, pll_io_b; + std::tie(pll_io_a, pll_io_b) = bel_pll.second; + if (bel2io.count(pll_io_a.bel)) { + if (pll_io_a.bel == pad_bel) + could_be_pad = !bel2io.count(pll_io_b.bel) || !is_sb_pll40_dual(ctx, ci); + continue; + } + if (bel2io.count(pll_io_b.bel) && is_sb_pll40_dual(ctx, ci)) + continue; + found_bel = bel_pll.first; + break; + } + + // Apply constrain & Inform user of result + if (found_bel == BelId()) + log_error("PLL '%s' couldn't be placed anywhere, no suitable BEL found.%s\n", ci->name.c_str(ctx), + could_be_pad ? " Did you mean to use a PAD PLL ?" : ""); + + log_info(" constrained PLL '%s' to %s\n", ci->name.c_str(ctx), ctx->getBelName(found_bel).c_str(ctx)); + if (could_be_pad) + log_info(" (given its connections, this PLL could have been a PAD PLL)\n"); + + ci->attrs[ctx->id("BEL")] = ctx->getBelName(found_bel).str(ctx); + pll_used_bels[found_bel] = ci; + } + } +} + // spliceLUT adds a pass-through LUT LC between the given cell's output port // and either all users or only non_LUT users. static std::unique_ptr spliceLUT(Context *ctx, CellInfo *ci, IdString portId, bool onlyNonLUTs) @@ -844,6 +1046,10 @@ static void pack_special(Context *ctx) ci->ports.erase(ctx->id("RGB0")); ci->ports.erase(ctx->id("RGB1")); ci->ports.erase(ctx->id("RGB2")); + } else if (is_sb_ledda_ip(ctx, ci)) { + /* Force placement (no choices anyway) */ + cell_place_unique(ctx, ci); + } else if (is_sb_pll40(ctx, ci)) { bool is_pad = is_sb_pll40_pad(ctx, ci); bool is_core = !is_pad; @@ -877,13 +1083,17 @@ static void pack_special(Context *ctx) } auto feedback_path = packed->params[ctx->id("FEEDBACK_PATH")]; - packed->params[ctx->id("FEEDBACK_PATH")] = - feedback_path == "DELAY" - ? "0" - : feedback_path == "SIMPLE" ? "1" - : feedback_path == "PHASE_AND_DELAY" - ? "2" - : feedback_path == "EXTERNAL" ? "6" : feedback_path; + std::string fbp_value = feedback_path == "DELAY" + ? "0" + : feedback_path == "SIMPLE" + ? "1" + : feedback_path == "PHASE_AND_DELAY" + ? "2" + : feedback_path == "EXTERNAL" ? "6" : feedback_path; + if (!std::all_of(fbp_value.begin(), fbp_value.end(), isdigit)) + log_error("PLL '%s' has unsupported FEEDBACK_PATH value '%s'\n", ci->name.c_str(ctx), + feedback_path.c_str()); + packed->params[ctx->id("FEEDBACK_PATH")] = fbp_value; packed->params[ctx->id("PLLTYPE")] = std::to_string(sb_pll40_type(ctx, ci)); NetInfo *pad_packagepin_net = nullptr; @@ -939,82 +1149,25 @@ static void pack_special(Context *ctx) replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname)); } - // If PLL is not constrained already, do that - we need this - // information to then constrain the LOCK LUT. - BelId pll_bel; - bool constrained = false; - if (packed->attrs.find(ctx->id("BEL")) == packed->attrs.end()) { - for (auto bel : ctx->getBels()) { - if (ctx->getBelType(bel) != id_ICESTORM_PLL) - continue; - if (ctx->isBelLocked(bel)) - continue; + // PLL must have been placed already in place_plls() + BelId pll_bel = ctx->getBelByName(ctx->id(packed->attrs[ctx->id("BEL")])); + NPNR_ASSERT(pll_bel != BelId()); - // A PAD PLL must have its' PACKAGEPIN on the SB_IO that's shared - // with PLLOUT_A. - if (is_pad) { - auto pll_sb_io_belpin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_A); - NPNR_ASSERT(pad_packagepin_net != nullptr); - auto pll_packagepin_driver = pad_packagepin_net->driver; - NPNR_ASSERT(pll_packagepin_driver.cell != nullptr); - if (pll_packagepin_driver.cell->type != ctx->id("SB_IO")) { - log_error("PLL '%s' has a PACKAGEPIN driven by " - "an %s, should be directly connected to an input SB_IO\n", - ci->name.c_str(ctx), pll_packagepin_driver.cell->type.c_str(ctx)); - } + // Deal with PAD PLL peculiarities + if (is_pad) { + NPNR_ASSERT(pad_packagepin_net != nullptr); + auto pll_packagepin_driver = pad_packagepin_net->driver; + NPNR_ASSERT(pll_packagepin_driver.cell != nullptr); + auto packagepin_cell = pll_packagepin_driver.cell; + auto packagepin_bel_name = packagepin_cell->attrs.find(ctx->id("BEL")); - auto packagepin_cell = pll_packagepin_driver.cell; - auto packagepin_bel_name = packagepin_cell->attrs.find(ctx->id("BEL")); - if (packagepin_bel_name == packagepin_cell->attrs.end()) { - log_error("PLL '%s' PACKAGEPIN SB_IO '%s' is unconstrained\n", ci->name.c_str(ctx), - packagepin_cell->name.c_str(ctx)); - } - auto packagepin_bel = ctx->getBelByName(ctx->id(packagepin_bel_name->second)); - if (pll_sb_io_belpin.bel != packagepin_bel) { - log_error("PLL '%s' PACKAGEPIN is connected to pin %s, can only be pin %s\n", - ci->name.c_str(ctx), ctx->getBelPackagePin(packagepin_bel).c_str(), - ctx->getBelPackagePin(pll_sb_io_belpin.bel).c_str()); - } - if (pad_packagepin_net->users.size() != 1) { - log_error("PLL '%s' clock input '%s' can only drive PLL\n", ci->name.c_str(ctx), - pad_packagepin_net->name.c_str(ctx)); - } - // Set an attribute about this PLL's PAD SB_IO. - packed->attrs[ctx->id("BEL_PAD_INPUT")] = packagepin_bel_name->second; - // Remove the connection from the SB_IO to the PLL. - packagepin_cell->ports.erase(pll_packagepin_driver.port); - } + // Set an attribute about this PLL's PAD SB_IO. + packed->attrs[ctx->id("BEL_PAD_INPUT")] = packagepin_bel_name->second; - log_info(" constrained PLL '%s' to %s\n", packed->name.c_str(ctx), - ctx->getBelName(bel).c_str(ctx)); - packed->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx); - pll_bel = bel; - constrained = true; - break; - } - if (!constrained) { - log_error("Could not constrain PLL '%s' to any PLL Bel (too many PLLs?)\n", - packed->name.c_str(ctx)); - } - } else { - pll_bel = ctx->getBelByName(ctx->id(packed->attrs[ctx->id("BEL")])); - if (ctx->getBelType(pll_bel) != id_ICESTORM_PLL) - log_error("PLL '%s' is constrained to BEL %s which isn't a ICESTORM_PLL BEL\n", - packed->name.c_str(ctx), ctx->getBelName(pll_bel).c_str(ctx)); - if (ctx->isBelLocked(pll_bel)) - log_error("PLL '%s' is constrained to locked BEL %s\n", packed->name.c_str(ctx), - ctx->getBelName(pll_bel).c_str(ctx)); - log_info(" constrained PLL '%s' to %s\n", packed->name.c_str(ctx), - ctx->getBelName(pll_bel).c_str(ctx)); - } - - // Delete the original PACKAGEPIN net if needed. - if (pad_packagepin_net != nullptr) { - for (auto user : pad_packagepin_net->users) { + // Disconnect PACKAGEPIN (it's a physical HW link) + 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); + packagepin_cell->ports.erase(pll_packagepin_driver.port); ctx->nets.erase(pad_packagepin_net->name); pad_packagepin_net = nullptr; } @@ -1118,8 +1271,10 @@ bool Arch::pack() pack_nonlut_ffs(ctx); pack_carries(ctx); pack_ram(ctx); + place_plls(ctx); pack_special(ctx); - promote_globals(ctx); + if (!bool_or_default(ctx->settings, ctx->id("no_promote_globals"), false)) + promote_globals(ctx); ctx->assignArchInfo(); constrain_chains(ctx); ctx->assignArchInfo(); diff --git a/ice40/picorv32_benchmark.py b/ice40/picorv32_benchmark.py index a4ec581e..5e4fc2e1 100755 --- a/ice40/picorv32_benchmark.py +++ b/ice40/picorv32_benchmark.py @@ -22,7 +22,7 @@ for i in range(num_runs): ascfile = "picorv32_work/picorv32_s{}.asc".format(run) if path.exists(ascfile): os.remove(ascfile) - result = subprocess.run(["../nextpnr-ice40", "--hx8k", "--seed", str(run), "--json", "picorv32.json", "--asc", ascfile, "--freq", "70"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) + result = subprocess.run(["../nextpnr-ice40", "--hx8k", "--seed", str(run), "--json", "picorv32.json", "--asc", ascfile, "--freq", "40", "--opt-timing"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) if result.returncode != 0: print("Run {} failed!".format(run)) else: diff --git a/json/jsonparse.cc b/json/jsonparse.cc index bddbee0b..b953fc7c 100644 --- a/json/jsonparse.cc +++ b/json/jsonparse.cc @@ -594,7 +594,11 @@ void json_import_cell(Context *ctx, string modname, const std::vector if (type == PORT_IN || type == PORT_INOUT) { net->users.push_back(pr); } else if (type == PORT_OUT) { - assert(net->driver.cell == nullptr); + if (net->driver.cell != nullptr) + log_error("multiple drivers on net '%s' (%s.%s and %s.%s)\n", + net->name.c_str(ctx), net->driver.cell->name.c_str(ctx), + net->driver.port.c_str(ctx), pr.cell->name.c_str(ctx), + pr.port.c_str(ctx)); net->driver = pr; } }