diff options
75 files changed, 1900 insertions, 382 deletions
diff --git a/.cirrus/Dockerfile.ubuntu20.04 b/.cirrus/Dockerfile.ubuntu20.04 index 4f229d2d..934bbbcb 100644 --- a/.cirrus/Dockerfile.ubuntu20.04 +++ b/.cirrus/Dockerfile.ubuntu20.04 @@ -65,4 +65,4 @@ RUN set -e -x ;\ PATH=$PATH:$HOME/.cargo/bin cargo install --path prjoxide RUN set -e -x ;\ - pip3 install apycula==0.0.1a12 + pip3 install apycula==0.2a2 diff --git a/.github/workflows/mistral_ci.yml b/.github/workflows/mistral_ci.yml index 7cf71621..64300d78 100644 --- a/.github/workflows/mistral_ci.yml +++ b/.github/workflows/mistral_ci.yml @@ -21,7 +21,7 @@ jobs: - name: Execute build nextpnr env: MISTRAL_PATH: ${{ github.workspace }}/deps/mistral - MISTRAL_REVISION: 0edeca112dda9bd463125feb869ddb7511d1acd9 + MISTRAL_REVISION: e039b595529ab573d9cb01c64ef927f9d81d63ce run: | source ./.github/ci/build_mistral.sh get_dependencies @@ -10,6 +10,7 @@ Currently nextpnr supports: * Lattice Nexus devices supported by [Project Oxide](https://github.com/gatecat/prjoxide) * Gowin LittleBee devices supported by [Project Apicula](https://github.com/YosysHQ/apicula) * *(experimental)* Cyclone V devices supported by [Mistral](https://github.com/Ravenslofty/mistral) + * *(experimental)* Lattice MachXO2 devices supported by [Project Trellis](https://github.com/YosysHQ/prjtrellis) * *(experimental)* a "generic" back-end for user-defined architectures There is some work in progress towards [support for Xilinx devices](https://github.com/gatecat/nextpnr-xilinx/) but it is not upstream and not intended for end users at the present time. We hope to see more FPGA families supported in the future. We would love your help in developing this awesome new project! diff --git a/common/arch_api.h b/common/arch_api.h index e49d26c1..14a30652 100644 --- a/common/arch_api.h +++ b/common/arch_api.h @@ -110,7 +110,7 @@ template <typename R> struct ArchAPI : BaseCtx virtual typename R::GroupPipsRangeT getGroupPips(GroupId group) const = 0; virtual typename R::GroupGroupsRangeT getGroupGroups(GroupId group) const = 0; // Delay Methods - virtual delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const = 0; + virtual delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const = 0; virtual delay_t getDelayEpsilon() const = 0; virtual delay_t getRipupDelayPenalty() const = 0; virtual float getDelayNS(delay_t v) const = 0; diff --git a/common/arch_pybindings_shared.h b/common/arch_pybindings_shared.h index f44aa70e..b3dc0506 100644 --- a/common/arch_pybindings_shared.h +++ b/common/arch_pybindings_shared.h @@ -48,6 +48,8 @@ fn_wrapper_2a_v<Context, decltype(&Context::copyBelPorts), &Context::copyBelPort fn_wrapper_1a<Context, decltype(&Context::getBelType), &Context::getBelType, conv_to_str<IdString>, conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelType"); +fn_wrapper_1a<Context, decltype(&Context::getBelLocation), &Context::getBelLocation, pass_through<Loc>, + conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelLocation"); fn_wrapper_1a<Context, decltype(&Context::checkBelAvail), &Context::checkBelAvail, pass_through<bool>, conv_from_str<BelId>>::def_wrap(ctx_cls, "checkBelAvail"); fn_wrapper_1a<Context, decltype(&Context::getBelChecksum), &Context::getBelChecksum, pass_through<uint32_t>, @@ -92,6 +94,8 @@ fn_wrapper_0a<Context, decltype(&Context::getPips), &Context::getPips, wrap_cont "getPips"); fn_wrapper_1a<Context, decltype(&Context::getPipChecksum), &Context::getPipChecksum, pass_through<uint32_t>, conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipChecksum"); +fn_wrapper_1a<Context, decltype(&Context::getPipLocation), &Context::getPipLocation, pass_through<Loc>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipLocation"); fn_wrapper_3a_v<Context, decltype(&Context::bindPip), &Context::bindPip, conv_from_str<PipId>, addr_and_unwrap<NetInfo>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindPip"); fn_wrapper_1a_v<Context, decltype(&Context::unbindPip), &Context::unbindPip, conv_from_str<PipId>>::def_wrap( diff --git a/common/command.cc b/common/command.cc index 5a13fb55..d4279a58 100644 --- a/common/command.cc +++ b/common/command.cc @@ -176,7 +176,9 @@ po::options_description CommandHandler::getGeneralOptions() general.add_options()("router2-heatmap", po::value<std::string>(), "prefix for router2 resource congestion heatmaps"); - general.add_options()("router2-tmg-ripup", "enable experimental timing-driven ripup in router2"); + general.add_options()("tmg-ripup", "enable experimental timing-driven ripup in router"); + general.add_options()("router2-tmg-ripup", + "enable experimental timing-driven ripup in router (deprecated; use --tmg-ripup instead)"); general.add_options()("report", po::value<std::string>(), "write timing and utilization report in JSON format to file"); @@ -298,8 +300,8 @@ void CommandHandler::setupContext(Context *ctx) ctx->settings[ctx->id("placerHeap/timingWeight")] = std::to_string(vm["placer-heap-timingweight"].as<int>()); if (vm.count("router2-heatmap")) ctx->settings[ctx->id("router2/heatmap")] = vm["router2-heatmap"].as<std::string>(); - if (vm.count("router2-tmg-ripup")) - ctx->settings[ctx->id("router2/tmg_ripup")] = true; + if (vm.count("tmg-ripup") || vm.count("router2-tmg-ripup")) + ctx->settings[ctx->id("router/tmg_ripup")] = true; // Setting default values if (ctx->settings.find(ctx->id("target_freq")) == ctx->settings.end()) diff --git a/common/context.cc b/common/context.cc index 6bba5cbe..faddf825 100644 --- a/common/context.cc +++ b/common/context.cc @@ -90,6 +90,25 @@ WireId Context::getNetinfoSinkWire(const NetInfo *net_info, const PortRef &sink, return WireId(); } +delay_t Context::predictArcDelay(const NetInfo *net_info, const PortRef &sink) const +{ + if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId() || sink.cell->bel == BelId()) + return 0; + IdString driver_pin, sink_pin; + // Pick the first pin for a prediction; assume all will be similar enouhg + for (auto pin : getBelPinsForCellPin(net_info->driver.cell, net_info->driver.port)) { + driver_pin = pin; + break; + } + for (auto pin : getBelPinsForCellPin(sink.cell, sink.port)) { + sink_pin = pin; + break; + } + if (driver_pin == IdString() || sink_pin == IdString()) + return 0; + return predictDelay(net_info->driver.cell->bel, driver_pin, sink.cell->bel, sink_pin); +} + delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &user_info) const { #ifdef ARCH_ECP5 @@ -98,7 +117,7 @@ delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &us #endif if (net_info->wires.empty()) - return predictDelay(net_info, user_info); + return predictArcDelay(net_info, user_info); WireId src_wire = getNetinfoSourceWire(net_info); if (src_wire == WireId()) @@ -128,7 +147,7 @@ delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &us if (cursor == src_wire) max_delay = std::max(max_delay, delay + getWireDelay(src_wire).maxDelay()); // routed else - max_delay = std::max(max_delay, predictDelay(net_info, user_info)); // unrouted + max_delay = std::max(max_delay, predictArcDelay(net_info, user_info)); // unrouted } return max_delay; } diff --git a/common/context.h b/common/context.h index 6adbbdb5..cb8fd257 100644 --- a/common/context.h +++ b/common/context.h @@ -51,6 +51,8 @@ struct Context : Arch, DeterministicRNG // -------------------------------------------------------------- + delay_t predictArcDelay(const NetInfo *net_info, const PortRef &sink) const; + WireId getNetinfoSourceWire(const NetInfo *net_info) const; SSOArray<WireId, 2> getNetinfoSinkWires(const NetInfo *net_info, const PortRef &sink) const; size_t getNetinfoSinkWireCount(const NetInfo *net_info, const PortRef &sink) const; diff --git a/common/hashlib.h b/common/hashlib.h index b71f0129..70de8c91 100644 --- a/common/hashlib.h +++ b/common/hashlib.h @@ -26,8 +26,11 @@ NEXTPNR_NAMESPACE_BEGIN const int hashtable_size_trigger = 2; const int hashtable_size_factor = 3; -// The XOR version of DJB2 -inline unsigned int mkhash(unsigned int a, unsigned int b) { return ((a << 5) + a) ^ b; } +// Cantor pairing function for two non-negative integers +// https://en.wikipedia.org/wiki/Pairing_function +inline unsigned int mkhash(unsigned int a, unsigned int b) { + return (a*a + 3*a + 2*a*b + b + b*b) / 2; +} // traditionally 5381 is used as starting value for the djb2 hash const unsigned int mkhash_init = 5381; diff --git a/common/nextpnr_namespaces.h b/common/nextpnr_namespaces.h index 6fb0aa77..b758d7c5 100644 --- a/common/nextpnr_namespaces.h +++ b/common/nextpnr_namespaces.h @@ -33,6 +33,8 @@ #define USING_NEXTPNR_NAMESPACE #endif +#define NPNR_UNUSED(x) ((void)x) + #if defined(__GNUC__) || defined(__clang__) #define NPNR_ATTRIBUTE(...) __attribute__((__VA_ARGS__)) #define NPNR_NORETURN __attribute__((noreturn)) diff --git a/common/place_common.cc b/common/place_common.cc index bcfa3633..e03fca55 100644 --- a/common/place_common.cc +++ b/common/place_common.cc @@ -50,7 +50,7 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type if (load_cell->bel == BelId()) continue; if (timing_driven) { - delay_t net_delay = ctx->predictDelay(net, load); + delay_t net_delay = ctx->predictArcDelay(net, load); auto slack = load.budget - net_delay; if (slack < 0) negative_slack += slack; diff --git a/common/placer1.cc b/common/placer1.cc index 4db1c951..6de035b4 100644 --- a/common/placer1.cc +++ b/common/placer1.cc @@ -866,11 +866,11 @@ class SAPlacer if (ctx->getPortTimingClass(net->driver.cell, net->driver.port, cc) == TMG_IGNORE) return 0; if (cfg.budgetBased) { - double delay = ctx->getDelayNS(ctx->predictDelay(net, net->users.at(user))); + double delay = ctx->getDelayNS(ctx->predictArcDelay(net, net->users.at(user))); return std::min(10.0, std::exp(delay - ctx->getDelayNS(net->users.at(user).budget) / 10)); } else { float crit = tmg.get_criticality(CellPortKey(net->users.at(user))); - double delay = ctx->getDelayNS(ctx->predictDelay(net, net->users.at(user))); + double delay = ctx->getDelayNS(ctx->predictArcDelay(net, net->users.at(user))); return delay * std::pow(crit, crit_exp); } } diff --git a/common/pybindings.cc b/common/pybindings.cc index f9ee9eb7..eef460ce 100644 --- a/common/pybindings.cc +++ b/common/pybindings.cc @@ -83,6 +83,8 @@ template <> struct string_converter<Property> } // namespace PythonConversion +std::string loc_repr_py(Loc loc) { return stringf("Loc(%d, %d, %d)", loc.x, loc.y, loc.z); } + PYBIND11_EMBEDDED_MODULE(MODULE_NAME, m) { py::register_exception_translator([](std::exception_ptr p) { @@ -175,7 +177,8 @@ PYBIND11_EMBEDDED_MODULE(MODULE_NAME, m) .def(py::init<int, int, int>()) .def_readwrite("x", &Loc::x) .def_readwrite("y", &Loc::y) - .def_readwrite("z", &Loc::z); + .def_readwrite("z", &Loc::z) + .def("__repr__", loc_repr_py); auto ci_cls = py::class_<ContextualWrapper<CellInfo &>>(m, "CellInfo"); readwrite_wrapper<CellInfo &, decltype(&CellInfo::name), &CellInfo::name, conv_to_str<IdString>, diff --git a/common/pywrappers.h b/common/pywrappers.h index 66dec6fb..60ef65be 100644 --- a/common/pywrappers.h +++ b/common/pywrappers.h @@ -257,7 +257,7 @@ template <typename Class, typename FuncT, FuncT fn, typename arg1_conv> struct f { Context *ctx = get_ctx<Class>(cls); Class &base = get_base<Class>(cls); - return (base.*fn)(arg1_conv()(ctx, arg1)); + (base.*fn)(arg1_conv()(ctx, arg1)); } template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } @@ -280,7 +280,7 @@ template <typename Class, typename FuncT, FuncT fn, typename arg1_conv, typename { Context *ctx = get_ctx<Class>(cls); Class &base = get_base<Class>(cls); - return (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2)); + (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2)); } template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } @@ -304,7 +304,7 @@ struct fn_wrapper_3a_v { Context *ctx = get_ctx<Class>(cls); Class &base = get_base<Class>(cls); - return (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3)); + (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3)); } template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } @@ -331,8 +331,7 @@ struct fn_wrapper_4a_v { Context *ctx = get_ctx<Class>(cls); Class &base = get_base<Class>(cls); - return (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), - arg4_conv()(ctx, arg4)); + (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), arg4_conv()(ctx, arg4)); } template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } @@ -360,8 +359,8 @@ struct fn_wrapper_5a_v { Context *ctx = get_ctx<Class>(cls); Class &base = get_base<Class>(cls); - return (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), - arg4_conv()(ctx, arg4), arg5_conv()(ctx, arg5)); + (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), arg4_conv()(ctx, arg4), + arg5_conv()(ctx, arg5)); } template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } @@ -390,8 +389,8 @@ struct fn_wrapper_6a_v { Context *ctx = get_ctx<Class>(cls); Class &base = get_base<Class>(cls); - return (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), - arg4_conv()(ctx, arg4), arg5_conv()(ctx, arg5), arg6_conv()(ctx, arg6)); + (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), arg4_conv()(ctx, arg4), + arg5_conv()(ctx, arg5), arg6_conv()(ctx, arg6)); } template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } diff --git a/common/router1.cc b/common/router1.cc index 0ff2bedd..f387aee1 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -117,14 +117,24 @@ struct Router1 int arcs_without_ripup = 0; bool ripup_flag; - Router1(Context *ctx, const Router1Cfg &cfg) : ctx(ctx), cfg(cfg) {} + TimingAnalyser tmg; + + bool timing_driven = true; + + Router1(Context *ctx, const Router1Cfg &cfg) : ctx(ctx), cfg(cfg), tmg(ctx) + { + timing_driven = ctx->setting<bool>("timing_driven"); + tmg.setup(); + tmg.run(); + } void arc_queue_insert(const arc_key &arc, WireId src_wire, WireId dst_wire) { if (queued_arcs.count(arc)) return; - delay_t pri = ctx->estimateDelay(src_wire, dst_wire) - arc.net_info->users[arc.user_idx].budget; + delay_t pri = ctx->estimateDelay(src_wire, dst_wire) * + (100 * tmg.get_criticality(CellPortKey(arc.net_info->users.at(arc.user_idx)))); arc_entry entry; entry.arc = arc; @@ -459,6 +469,8 @@ struct Router1 auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx], arc.phys_idx); ripup_flag = false; + float crit = tmg.get_criticality(CellPortKey(net_info->users.at(user_idx))); + if (ctx->debug) { log("Routing arc %d on net %s (%d arcs total):\n", user_idx, ctx->nameOf(net_info), int(net_info->users.size())); @@ -536,6 +548,7 @@ struct Router1 delay_t next_delay = qw.delay + ctx->getPipDelay(pip).maxDelay(); delay_t next_penalty = qw.penalty; delay_t next_bonus = qw.bonus; + delay_t penalty_delta = 0; WireId next_wire = ctx->getPipDstWire(pip); next_delay += ctx->getWireDelay(next_wire).maxDelay(); @@ -544,7 +557,7 @@ struct Router1 NetInfo *conflictWireNet = nullptr, *conflictPipNet = nullptr; if (net_info->wires.count(next_wire) && net_info->wires.at(next_wire).pip == pip) { - next_bonus += cfg.reuseBonus; + next_bonus += cfg.reuseBonus * (1.0 - crit); } else { if (!ctx->checkWireAvail(next_wire)) { if (!ripup) @@ -609,34 +622,36 @@ struct Router1 if (conflictWireWire != WireId()) { auto scores_it = wireScores.find(conflictWireWire); if (scores_it != wireScores.end()) - next_penalty += scores_it->second * cfg.wireRipupPenalty; - next_penalty += cfg.wireRipupPenalty; + penalty_delta += scores_it->second * cfg.wireRipupPenalty; + penalty_delta += cfg.wireRipupPenalty; } if (conflictPipWire != WireId()) { auto scores_it = wireScores.find(conflictPipWire); if (scores_it != wireScores.end()) - next_penalty += scores_it->second * cfg.wireRipupPenalty; - next_penalty += cfg.wireRipupPenalty; + penalty_delta += scores_it->second * cfg.wireRipupPenalty; + penalty_delta += cfg.wireRipupPenalty; } if (conflictWireNet != nullptr) { auto scores_it = netScores.find(conflictWireNet); if (scores_it != netScores.end()) - next_penalty += scores_it->second * cfg.netRipupPenalty; - next_penalty += cfg.netRipupPenalty; - next_penalty += conflictWireNet->wires.size() * cfg.wireRipupPenalty; + penalty_delta += scores_it->second * cfg.netRipupPenalty; + penalty_delta += cfg.netRipupPenalty; + penalty_delta += conflictWireNet->wires.size() * cfg.wireRipupPenalty; } if (conflictPipNet != nullptr) { auto scores_it = netScores.find(conflictPipNet); if (scores_it != netScores.end()) - next_penalty += scores_it->second * cfg.netRipupPenalty; - next_penalty += cfg.netRipupPenalty; - next_penalty += conflictPipNet->wires.size() * cfg.wireRipupPenalty; + penalty_delta += scores_it->second * cfg.netRipupPenalty; + penalty_delta += cfg.netRipupPenalty; + penalty_delta += conflictPipNet->wires.size() * cfg.wireRipupPenalty; } } + next_penalty += penalty_delta * (timing_driven ? std::max(0.05, (1.0 - crit)) : 1); + delay_t next_score = next_delay + next_penalty; NPNR_ASSERT(next_score >= 0); @@ -778,6 +793,53 @@ struct Router1 return true; } + + delay_t find_slack_thresh() + { + // If more than 5% of arcs have negative slack; use the 5% threshold as a ripup criteria + int arc_count = 0; + int failed_count = 0; + delay_t default_thresh = ctx->getDelayEpsilon(); + + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (skip_net(ni)) + continue; + for (size_t i = 0; i < ni->users.size(); i++) { + auto &usr = ni->users.at(i); + ++arc_count; + delay_t slack = tmg.get_setup_slack(CellPortKey(usr)); + if (slack == std::numeric_limits<delay_t>::min()) + continue; + if (slack < default_thresh) + ++failed_count; + } + } + + if (arc_count < 50 || (failed_count < (0.05 * arc_count))) { + return default_thresh; + } + + std::vector<delay_t> slacks; + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (skip_net(ni)) + continue; + for (size_t i = 0; i < ni->users.size(); i++) { + auto &usr = ni->users.at(i); + delay_t slack = tmg.get_setup_slack(CellPortKey(usr)); + if (slack == std::numeric_limits<delay_t>::min()) + continue; + slacks.push_back(slack); + } + } + std::sort(slacks.begin(), slacks.end()); + delay_t thresh = slacks.at(int(slacks.size() * 0.05)); + log_warning("%.f%% of arcs have failing slack; using %.2fns as ripup threshold. Consider a reduced Fmax " + "constraint.\n", + (100.0 * failed_count) / arc_count, ctx->getDelayNS(thresh)); + return thresh; + } }; } // namespace @@ -819,6 +881,9 @@ bool router1(Context *ctx, const Router1Cfg &cfg) int iter_cnt = 0; int last_arcs_with_ripup = 0; int last_arcs_without_ripup = 0; + int timing_fail_count = 0; + bool timing_ripup = ctx->setting<bool>("router/tmg_ripup", false); + delay_t ripup_slack = 0; log_info(" | (re-)routed arcs | delta | remaining| time spent |\n"); log_info(" IterCnt | w/ripup wo/ripup | w/r wo/r | arcs| batch(sec) total(sec)|\n"); @@ -854,6 +919,48 @@ bool router1(Context *ctx, const Router1Cfg &cfg) #endif return false; } + // Timing driven ripup + if (timing_ripup && router.arc_queue.empty() && timing_fail_count < 50) { + ++timing_fail_count; + router.tmg.run(); + delay_t wns = 0, tns = 0; + if (timing_fail_count == 1) + ripup_slack = router.find_slack_thresh(); + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (router.skip_net(ni)) + continue; + bool is_locked = false; + for (auto &wire : ni->wires) { + if (wire.second.strength > STRENGTH_STRONG) + is_locked = true; + } + if (is_locked) + continue; + for (size_t i = 0; i < ni->users.size(); i++) { + auto &usr = ni->users.at(i); + delay_t slack = router.tmg.get_setup_slack(CellPortKey(usr)); + if (slack == std::numeric_limits<delay_t>::min()) + continue; + if (slack < 0) { + wns = std::min(wns, slack); + tns += slack; + } + if (slack <= ripup_slack) { + for (WireId w : ctx->getNetinfoSinkWires(ni, usr)) { + if (ctx->checkWireAvail(w)) + continue; + router.ripup_wire(w); + } + } + } + } + log_info(" %d arcs ripped up due to negative slack WNS=%.02fns TNS=%.02fns.\n", + int(router.arc_queue.size()), ctx->getDelayNS(wns), ctx->getDelayNS(tns)); + iter_cnt = 0; + router.wireScores.clear(); + router.netScores.clear(); + } } auto rend = std::chrono::high_resolution_clock::now(); log_info("%10d | %8d %10d | %4d %5d | %9d| %10.02f %10.02f|\n", iter_cnt, router.arcs_with_ripup, diff --git a/common/router2.cc b/common/router2.cc index d713cce2..c76e1f61 100644 --- a/common/router2.cc +++ b/common/router2.cc @@ -1373,8 +1373,8 @@ struct Router2 route_queue.push_back(i); timing_driven = ctx->setting<bool>("timing_driven"); - if (ctx->settings.count(ctx->id("router2/tmg_ripup"))) - timing_driven_ripup = timing_driven && ctx->setting<bool>("router2/tmg_ripup"); + if (ctx->settings.count(ctx->id("router/tmg_ripup"))) + timing_driven_ripup = timing_driven && ctx->setting<bool>("router/tmg_ripup"); else timing_driven_ripup = false; log_info("Running main router loop...\n"); diff --git a/common/sso_array.h b/common/sso_array.h index 1fae6c57..80e7d1c1 100644 --- a/common/sso_array.h +++ b/common/sso_array.h @@ -70,6 +70,26 @@ template <typename T, std::size_t N> class SSOArray std::copy(other.begin(), other.end(), begin()); } + SSOArray(SSOArray &&other) : m_size(other.size()) + { + if (is_heap()) + data_heap = other.data_heap; + else + std::copy(other.begin(), other.end(), begin()); + other.m_size = 0; + } + SSOArray &operator=(const SSOArray &other) + { + if (&other == this) + return *this; + if (is_heap()) + delete[] data_heap; + m_size = other.m_size; + alloc(); + std::copy(other.begin(), other.end(), begin()); + return *this; + } + template <typename Tother> SSOArray(const Tother &other) : m_size(other.size()) { alloc(); diff --git a/common/timing.cc b/common/timing.cc index e305d82d..f30d4fc5 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -1378,7 +1378,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p auto driver_wire = ctx->getNetinfoSourceWire(net); auto sink_wire = ctx->getNetinfoSinkWire(net, sink_ref, 0); log_info(" prediction: %f ns estimate: %f ns\n", - ctx->getDelayNS(ctx->predictDelay(net, sink_ref)), + ctx->getDelayNS(ctx->predictArcDelay(net, sink_ref)), ctx->getDelayNS(ctx->estimateDelay(driver_wire, sink_wire))); auto cursor = sink_wire; delay_t delay; diff --git a/common/timing_opt.cc b/common/timing_opt.cc index 6dd93d67..a73a70cf 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -99,7 +99,7 @@ class TimingOptimiser continue; for (auto user : net->users) { if (user.cell == cell && user.port == port.first) { - if (ctx->predictDelay(net, user) > + if (ctx->predictArcDelay(net, user) > 1.1 * max_net_delay.at(std::make_pair(cell->name, port.first))) return false; } @@ -111,7 +111,7 @@ class TimingOptimiser BelId dstBel = user.cell->bel; if (dstBel == BelId()) continue; - if (ctx->predictDelay(net, user) > + if (ctx->predictArcDelay(net, user) > 1.1 * max_net_delay.at(std::make_pair(user.cell->name, user.port))) { return false; @@ -413,7 +413,7 @@ class TimingOptimiser 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); + original_delay += ctx->predictArcDelay(pn, usr); break; } } @@ -497,7 +497,7 @@ class TimingOptimiser 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); + total_delay += ctx->predictArcDelay(pn, usr); break; } } diff --git a/docs/archapi.md b/docs/archapi.md index f2571f08..f798254f 100644 --- a/docs/archapi.md +++ b/docs/archapi.md @@ -280,7 +280,7 @@ Return a (preferably unique) number that represents this wire. This is used in d ### void bindWire(WireId wire, NetInfo \*net, PlaceStrength strength) -Bind a wire to a net. This method must be used when binding a wire that is driven by a bel pin. Use `binPip()` +Bind a wire to a net. This method must be used when binding a wire that is driven by a bel pin. Use `bindPip()` when binding a wire that is driven by a pip. This method must also update `net->wires`. @@ -517,7 +517,7 @@ result, and for that estimate it is considered more acceptable to return a slightly too high result and it is considered less acceptable to return a too low result (thus "low upper bound"). -### delay\_t predictDelay(const NetInfo \*net\_info, const PortRef &sink) const +### delay\_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const Return a reasonably good estimate for the total `maxDelay()` delay for the given arc. This should return a low upper bound for the fastest route for that arc. diff --git a/docs/coding.md b/docs/coding.md index dc09bb3f..db2ec079 100644 --- a/docs/coding.md +++ b/docs/coding.md @@ -80,7 +80,7 @@ As nextpnr allows arbitrary constraints on bels for more advanced packer-free fl There are several routes for timing information in the placer: - sink `PortRef`s have a `budget` value annotated by calling `assign_budget` which is an estimate of the maximum delay that an arc may have - sink ports can have a criticality (value between 0 and 1 where 1 is the critical path) associated with them by using `get_criticalities` and a `NetCriticalityMap` - - `predictDelay` returns an estimated delay for a sink port based on placement information + - `predictDelay` and its derivative `predictArcDelay` returns an estimated delay for a sink port based on placement information ### Bel Buckets diff --git a/docs/generic.md b/docs/generic.md index 0562bed3..96db872e 100644 --- a/docs/generic.md +++ b/docs/generic.md @@ -12,35 +12,42 @@ will be worked on in the future. ## Python API -All identifiers (`IdString`) are automatically converted to -and from a Python string, so no manual conversion is required. +All identifiers (`IdString`, `IdStringList`, `WireId`, `PipId`, and `BelId`) are +automatically converted to and from a Python string, so no manual conversion is +required. + +`IdStringList`s will be most efficient if strings can be split according to a +separator (currently fixed to `/`), as only the components need be stored +in-memory. For example; instead of needing to store an entire pip name +`X33/Y45/V4A_TO_A6` which scales badly for large numbers of pips; the strings +`X33`, `Y45` and `V4A_TO_A6` are stored. Argument names are included in the Python bindings, so named arguments may be used. -### void addWire(IdString name, IdString type, int x, int y); +### void addWire(IdStringList name, IdString type, int x, int y); Adds a wire with a name, type (for user purposes only, ignored by all nextpnr code other than the UI) to the FPGA description. x and y give a nominal location of the wire for delay estimation purposes. Delay estimates are important for router performance (as the router uses an A* type algorithm), even if timing is not of importance. -### addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, float delay, Loc loc); +### addPip(IdStringList name, IdString type, WireId srcWire, WireId dstWire, float delay, Loc loc); Adds a pip (programmable connection between two named wires). Pip delays that correspond to delay estimates are important for router performance (as the router uses an A* type algorithm), even if timing is otherwise not of importance. Loc is constructed using `Loc(x, y, z)`. 'z' for pips is only important if region constraints (e.g. for partial reconfiguration regions) are used. -### void addBel(IdString name, IdString type, Loc loc, bool gb, bool hidden); +### void addBel(IdStringList name, IdString type, Loc loc, bool gb, bool hidden); Adds a bel to the FPGA description. Bel type should match the type of cells in the netlist that are placed at this bel (see below for information on special bel types supported by the packer). Loc is constructed using `Loc(x, y, z)` and must be unique. If `hidden` is true, then the bel will not be included in utilisation reports (e.g. for routing/internal use bels). -### void addBelInput(IdString bel, IdString name, IdString wire); -### void addBelOutput(IdString bel, IdString name, IdString wire); -### void addBelInout(IdString bel, IdString name, IdString wire); +### void addBelInput(BelId bel, IdString name, WireId wire); +### void addBelOutput(BelId bel, IdString name, WireId wire); +### void addBelInout(BelId bel, IdString name, WireId wire); Adds an input, output or inout pin to a bel, with an associated wire. Note that both `bel` and `wire` must have been created before calling this function. -### void addGroupBel(IdString group, IdString bel); -### void addGroupWire(IdString group, IdString wire); -### void addGroupPip(IdString group, IdString pip); +### void addGroupBel(IdString group, BelId bel); +### void addGroupWire(IdString group, WireId wire); +### void addGroupPip(IdString group, PipId pip); ### void addGroupGroup(IdString group, IdString grp); Add a bel, wire, pip or subgroup to a group, which will be created if it doesn't already exist. Groups are purely for visual presentation purposes in the user interface and are not used by any place-and-route algorithms. @@ -56,9 +63,9 @@ Add a graphic element to a _decal_, a reusable drawing that may be used to repre Sets the decal ID and offset for a wire, bel, pip or group in the UI. -### void setWireAttr(IdString wire, IdString key, const std::string &value); -### void setPipAttr(IdString pip, IdString key, const std::string &value); -### void setBelAttr(IdString bel, IdString key, const std::string &value); +### void setWireAttr(WireId wire, IdString key, const std::string &value); +### void setPipAttr(PipId pip, IdString key, const std::string &value); +### void setBelAttr(BelId bel, IdString key, const std::string &value); Sets an attribute on a wire, pip or bel. Attributes are displayed in the tree view in the UI, but have no bearing on place-and-route itself. diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 95a27682..2e453f2a 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -543,26 +543,21 @@ ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const return bb; } -delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const { - const auto &driver = net_info->driver; - if ((driver.port == id_FCO && sink.port == id_FCI) || sink.port == id_FXA || sink.port == id_FXB) + if ((src_pin == id_FCO && dst_pin == id_FCI) || dst_pin == id_FXA || dst_pin == id_FXB) return 0; - auto driver_loc = getBelLocation(driver.cell->bel); - auto sink_loc = getBelLocation(sink.cell->bel); + auto driver_loc = getBelLocation(src_bel); + auto sink_loc = getBelLocation(dst_bel); // Encourage use of direct interconnect if (driver_loc.x == sink_loc.x && driver_loc.y == sink_loc.y) { - if ((sink.port == id_A0 || sink.port == id_A1) && (driver.port == id_F1) && - (driver_loc.z == 2 || driver_loc.z == 3)) + if ((dst_pin == id_A0 || dst_pin == id_A1) && (src_pin == id_F1) && (driver_loc.z == 2 || driver_loc.z == 3)) return 0; - if ((sink.port == id_B0 || sink.port == id_B1) && (driver.port == id_F1) && - (driver_loc.z == 0 || driver_loc.z == 1)) + if ((dst_pin == id_B0 || dst_pin == id_B1) && (src_pin == id_F1) && (driver_loc.z == 0 || driver_loc.z == 1)) return 0; - if ((sink.port == id_C0 || sink.port == id_C1) && (driver.port == id_F0) && - (driver_loc.z == 2 || driver_loc.z == 3)) + if ((dst_pin == id_C0 || dst_pin == id_C1) && (src_pin == id_F0) && (driver_loc.z == 2 || driver_loc.z == 3)) return 0; - if ((sink.port == id_D0 || sink.port == id_D1) && (driver.port == id_F0) && - (driver_loc.z == 0 || driver_loc.z == 1)) + if ((dst_pin == id_D0 || dst_pin == id_D1) && (src_pin == id_F0) && (driver_loc.z == 0 || driver_loc.z == 1)) return 0; } diff --git a/ecp5/arch.h b/ecp5/arch.h index 51a919bb..c1bed2b3 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -913,7 +913,7 @@ struct Arch : BaseArch<ArchRanges> delay_t estimateDelay(WireId src, WireId dst) const override; ArcBounds getRouteBoundingBox(WireId src, WireId dst) const override; - delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override; + delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const override; delay_t getDelayEpsilon() const override { return 20; } delay_t getRipupDelayPenalty() const override; float getDelayNS(delay_t v) const override { return v * 0.001; } diff --git a/fpga_interchange/arch.cc b/fpga_interchange/arch.cc index a39f49e6..917af85e 100644 --- a/fpga_interchange/arch.cc +++ b/fpga_interchange/arch.cc @@ -1000,14 +1000,16 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const #endif } -delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const { // FIXME: Implement when adding timing-driven place and route. + NPNR_UNUSED(src_pin); + NPNR_UNUSED(dst_pin); int src_x, src_y; - get_tile_x_y(net_info->driver.cell->bel.tile, &src_x, &src_y); + get_tile_x_y(src_bel.tile, &src_x, &src_y); int dst_x, dst_y; - get_tile_x_y(sink.cell->bel.tile, &dst_x, &dst_y); + get_tile_x_y(dst_bel.tile, &dst_x, &dst_y); delay_t base = 30 * std::min(std::abs(dst_x - src_x), 18) + 10 * std::max(std::abs(dst_x - src_x) - 18, 0) + 60 * std::min(std::abs(dst_y - src_y), 6) + 20 * std::max(std::abs(dst_y - src_y) - 6, 0) + 300; diff --git a/fpga_interchange/arch.h b/fpga_interchange/arch.h index 482bf911..8bb2e2d1 100644 --- a/fpga_interchange/arch.h +++ b/fpga_interchange/arch.h @@ -700,7 +700,7 @@ struct Arch : ArchAPI<ArchRanges> // ------------------------------------------------- delay_t estimateDelay(WireId src, WireId dst) const final; - delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const final; + delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const final; ArcBounds getRouteBoundingBox(WireId src, WireId dst) const final; delay_t getDelayEpsilon() const final { return 20; } delay_t getRipupDelayPenalty() const final { return 120; } diff --git a/frontend/frontend_base.h b/frontend/frontend_base.h index 6d2ee6f6..ed9354b6 100644 --- a/frontend/frontend_base.h +++ b/frontend/frontend_base.h @@ -229,7 +229,7 @@ template <typename FrontendType> struct GenericFrontend } name = ctx->id(comb); incr++; - } while (is_net ? ctx->nets.count(name) : ctx->cells.count(name)); + } while (is_net ? (ctx->nets.count(name) || ctx->net_aliases.count(name)) : ctx->cells.count(name)); return name; } diff --git a/generic/arch.cc b/generic/arch.cc index ebc1ef26..ad054efd 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -25,49 +25,31 @@ #include "router1.h" #include "router2.h" #include "util.h" +#include "viaduct_api.h" NEXTPNR_NAMESPACE_BEGIN -WireInfo &Arch::wire_info(IdStringList wire) +WireId Arch::addWire(IdStringList name, IdString type, int x, int y) { - auto w = wires.find(wire); - if (w == wires.end()) - NPNR_ASSERT_FALSE_STR("no wire named " + wire.str(getCtx())); - return w->second; -} - -PipInfo &Arch::pip_info(IdStringList pip) -{ - auto p = pips.find(pip); - if (p == pips.end()) - NPNR_ASSERT_FALSE_STR("no pip named " + pip.str(getCtx())); - return p->second; -} - -BelInfo &Arch::bel_info(IdStringList bel) -{ - auto b = bels.find(bel); - if (b == bels.end()) - NPNR_ASSERT_FALSE_STR("no bel named " + bel.str(getCtx())); - return b->second; -} - -void Arch::addWire(IdStringList name, IdString type, int x, int y) -{ - NPNR_ASSERT(wires.count(name) == 0); - WireInfo &wi = wires[name]; + NPNR_ASSERT(wire_by_name.count(name) == 0); + WireId wire(wires.size()); + wire_by_name[name] = wire; + wires.emplace_back(); + WireInfo &wi = wires.back(); wi.name = name; wi.type = type; wi.x = x; wi.y = y; - - wire_ids.push_back(name); + return wire; } -void Arch::addPip(IdStringList name, IdString type, IdStringList srcWire, IdStringList dstWire, delay_t delay, Loc loc) +PipId Arch::addPip(IdStringList name, IdString type, WireId srcWire, WireId dstWire, delay_t delay, Loc loc) { - NPNR_ASSERT(pips.count(name) == 0); - PipInfo &pi = pips[name]; + NPNR_ASSERT(pip_by_name.count(name) == 0); + PipId pip(pips.size()); + pip_by_name[name] = pip; + pips.emplace_back(); + PipInfo &pi = pips.back(); pi.name = name; pi.type = type; pi.srcWire = srcWire; @@ -75,9 +57,8 @@ void Arch::addPip(IdStringList name, IdString type, IdStringList srcWire, IdStri pi.delay = delay; pi.loc = loc; - wire_info(srcWire).downhill.push_back(name); - wire_info(dstWire).uphill.push_back(name); - pip_ids.push_back(name); + wire_info(srcWire).downhill.push_back(pip); + wire_info(dstWire).uphill.push_back(pip); if (int(tilePipDimZ.size()) <= loc.x) tilePipDimZ.resize(loc.x + 1); @@ -88,13 +69,17 @@ void Arch::addPip(IdStringList name, IdString type, IdStringList srcWire, IdStri gridDimX = std::max(gridDimX, loc.x + 1); gridDimY = std::max(gridDimY, loc.x + 1); tilePipDimZ[loc.x][loc.y] = std::max(tilePipDimZ[loc.x][loc.y], loc.z + 1); + return pip; } -void Arch::addBel(IdStringList name, IdString type, Loc loc, bool gb, bool hidden) +BelId Arch::addBel(IdStringList name, IdString type, Loc loc, bool gb, bool hidden) { - NPNR_ASSERT(bels.count(name) == 0); + NPNR_ASSERT(bel_by_name.count(name) == 0); NPNR_ASSERT(bel_by_loc.count(loc) == 0); - BelInfo &bi = bels[name]; + BelId bel(bels.size()); + bel_by_name[name] = bel; + bels.emplace_back(); + BelInfo &bi = bels.back(); bi.name = name; bi.type = type; bi.x = loc.x; @@ -103,8 +88,7 @@ void Arch::addBel(IdStringList name, IdString type, Loc loc, bool gb, bool hidde bi.gb = gb; bi.hidden = hidden; - bel_ids.push_back(name); - bel_by_loc[loc] = name; + bel_by_loc[loc] = bel; if (int(bels_by_tile.size()) <= loc.x) bels_by_tile.resize(loc.x + 1); @@ -112,7 +96,7 @@ void Arch::addBel(IdStringList name, IdString type, Loc loc, bool gb, bool hidde if (int(bels_by_tile[loc.x].size()) <= loc.y) bels_by_tile[loc.x].resize(loc.y + 1); - bels_by_tile[loc.x][loc.y].push_back(name); + bels_by_tile[loc.x][loc.y].push_back(bel); if (int(tileBelDimZ.size()) <= loc.x) tileBelDimZ.resize(loc.x + 1); @@ -123,49 +107,50 @@ void Arch::addBel(IdStringList name, IdString type, Loc loc, bool gb, bool hidde gridDimX = std::max(gridDimX, loc.x + 1); gridDimY = std::max(gridDimY, loc.x + 1); tileBelDimZ[loc.x][loc.y] = std::max(tileBelDimZ[loc.x][loc.y], loc.z + 1); + return bel; } -void Arch::addBelInput(IdStringList bel, IdString name, IdStringList wire) +void Arch::addBelInput(BelId bel, IdString name, WireId wire) { - NPNR_ASSERT(bel_info(bel).pins.count(name) == 0); - PinInfo &pi = bel_info(bel).pins[name]; + auto &bi = bel_info(bel); + NPNR_ASSERT(bi.pins.count(name) == 0); + PinInfo &pi = bi.pins[name]; pi.name = name; pi.wire = wire; pi.type = PORT_IN; - wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name}); wire_info(wire).bel_pins.push_back(BelPin{bel, name}); } -void Arch::addBelOutput(IdStringList bel, IdString name, IdStringList wire) +void Arch::addBelOutput(BelId bel, IdString name, WireId wire) { - NPNR_ASSERT(bel_info(bel).pins.count(name) == 0); - PinInfo &pi = bel_info(bel).pins[name]; + auto &bi = bel_info(bel); + NPNR_ASSERT(bi.pins.count(name) == 0); + PinInfo &pi = bi.pins[name]; pi.name = name; pi.wire = wire; pi.type = PORT_OUT; - wire_info(wire).uphill_bel_pin = BelPin{bel, name}; wire_info(wire).bel_pins.push_back(BelPin{bel, name}); } -void Arch::addBelInout(IdStringList bel, IdString name, IdStringList wire) +void Arch::addBelInout(BelId bel, IdString name, WireId wire) { - NPNR_ASSERT(bel_info(bel).pins.count(name) == 0); - PinInfo &pi = bel_info(bel).pins[name]; + auto &bi = bel_info(bel); + NPNR_ASSERT(bi.pins.count(name) == 0); + PinInfo &pi = bi.pins[name]; pi.name = name; pi.wire = wire; pi.type = PORT_INOUT; - wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name}); wire_info(wire).bel_pins.push_back(BelPin{bel, name}); } -void Arch::addGroupBel(IdStringList group, IdStringList bel) { groups[group].bels.push_back(bel); } +void Arch::addGroupBel(IdStringList group, BelId bel) { groups[group].bels.push_back(bel); } -void Arch::addGroupWire(IdStringList group, IdStringList wire) { groups[group].wires.push_back(wire); } +void Arch::addGroupWire(IdStringList group, WireId wire) { groups[group].wires.push_back(wire); } -void Arch::addGroupPip(IdStringList group, IdStringList pip) { groups[group].pips.push_back(pip); } +void Arch::addGroupPip(IdStringList group, PipId pip) { groups[group].pips.push_back(pip); } void Arch::addGroupGroup(IdStringList group, IdStringList grp) { groups[group].groups.push_back(grp); } @@ -177,19 +162,19 @@ void Arch::addDecalGraphic(DecalId decal, const GraphicElement &graphic) void Arch::setWireDecal(WireId wire, DecalXY decalxy) { - wire_info(wire).decalxy = decalxy; + wires.at(wire.index).decalxy = decalxy; refreshUiWire(wire); } void Arch::setPipDecal(PipId pip, DecalXY decalxy) { - pip_info(pip).decalxy = decalxy; + pips.at(pip.index).decalxy = decalxy; refreshUiPip(pip); } void Arch::setBelDecal(BelId bel, DecalXY decalxy) { - bel_info(bel).decalxy = decalxy; + bels.at(bel.index).decalxy = decalxy; refreshUiBel(bel); } @@ -199,14 +184,11 @@ void Arch::setGroupDecal(GroupId group, DecalXY decalxy) refreshUiGroup(group); } -void Arch::setWireAttr(IdStringList wire, IdString key, const std::string &value) -{ - wire_info(wire).attrs[key] = value; -} +void Arch::setWireAttr(WireId wire, IdString key, const std::string &value) { wire_info(wire).attrs[key] = value; } -void Arch::setPipAttr(IdStringList pip, IdString key, const std::string &value) { pip_info(pip).attrs[key] = value; } +void Arch::setPipAttr(PipId pip, IdString key, const std::string &value) { pip_info(pip).attrs[key] = value; } -void Arch::setBelAttr(IdStringList bel, IdString key, const std::string &value) { bel_info(bel).attrs[key] = value; } +void Arch::setBelAttr(BelId bel, IdString key, const std::string &value) { bel_info(bel).attrs[key] = value; } void Arch::setLutK(int K) { args.K = K; } @@ -268,16 +250,19 @@ void IdString::initialize_arch(const BaseCtx *ctx) {} BelId Arch::getBelByName(IdStringList name) const { - if (bels.count(name)) - return name; - return BelId(); + if (name.size() == 0) + return BelId(); + auto fnd = bel_by_name.find(name); + if (fnd == bel_by_name.end()) + NPNR_ASSERT_FALSE_STR("no bel named " + name.str(getCtx())); + return fnd->second; } -IdStringList Arch::getBelName(BelId bel) const { return bel; } +IdStringList Arch::getBelName(BelId bel) const { return bel_info(bel).name; } Loc Arch::getBelLocation(BelId bel) const { - auto &info = bels.at(bel); + auto &info = bel_info(bel); return Loc(info.x, info.y, info.z); } @@ -291,7 +276,7 @@ BelId Arch::getBelByLocation(Loc loc) const const std::vector<BelId> &Arch::getBelsByTile(int x, int y) const { return bels_by_tile.at(x).at(y); } -bool Arch::getBelGlobalBuf(BelId bel) const { return bels.at(bel).gb; } +bool Arch::getBelGlobalBuf(BelId bel) const { return bel_info(bel).gb; } uint32_t Arch::getBelChecksum(BelId bel) const { @@ -301,7 +286,9 @@ uint32_t Arch::getBelChecksum(BelId bel) const void Arch::bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) { - bels.at(bel).bound_cell = cell; + if (uarch) + uarch->notifyBelChange(bel, cell); + bel_info(bel).bound_cell = cell; cell->bel = bel; cell->belStrength = strength; refreshUiBel(bel); @@ -309,40 +296,46 @@ void Arch::bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) void Arch::unbindBel(BelId bel) { - bels.at(bel).bound_cell->bel = BelId(); - bels.at(bel).bound_cell->belStrength = STRENGTH_NONE; - bels.at(bel).bound_cell = nullptr; + if (uarch) + uarch->notifyBelChange(bel, nullptr); + auto &bi = bel_info(bel); + bi.bound_cell->bel = BelId(); + bi.bound_cell->belStrength = STRENGTH_NONE; + bi.bound_cell = nullptr; refreshUiBel(bel); } -bool Arch::checkBelAvail(BelId bel) const { return bels.at(bel).bound_cell == nullptr; } +bool Arch::checkBelAvail(BelId bel) const +{ + return (!uarch || uarch->checkBelAvail(bel)) && (bel_info(bel).bound_cell == nullptr); +} -CellInfo *Arch::getBoundBelCell(BelId bel) const { return bels.at(bel).bound_cell; } +CellInfo *Arch::getBoundBelCell(BelId bel) const { return bel_info(bel).bound_cell; } -CellInfo *Arch::getConflictingBelCell(BelId bel) const { return bels.at(bel).bound_cell; } +CellInfo *Arch::getConflictingBelCell(BelId bel) const { return bel_info(bel).bound_cell; } -const std::vector<BelId> &Arch::getBels() const { return bel_ids; } +linear_range<BelId> Arch::getBels() const { return linear_range<BelId>(bels.size()); } -IdString Arch::getBelType(BelId bel) const { return bels.at(bel).type; } +IdString Arch::getBelType(BelId bel) const { return bel_info(bel).type; } -bool Arch::getBelHidden(BelId bel) const { return bels.at(bel).hidden; } +bool Arch::getBelHidden(BelId bel) const { return bel_info(bel).hidden; } -const std::map<IdString, std::string> &Arch::getBelAttrs(BelId bel) const { return bels.at(bel).attrs; } +const std::map<IdString, std::string> &Arch::getBelAttrs(BelId bel) const { return bel_info(bel).attrs; } WireId Arch::getBelPinWire(BelId bel, IdString pin) const { - const auto &bdata = bels.at(bel); + const auto &bdata = bel_info(bel); if (!bdata.pins.count(pin)) log_error("bel '%s' has no pin '%s'\n", getCtx()->nameOfBel(bel), pin.c_str(this)); return bdata.pins.at(pin).wire; } -PortType Arch::getBelPinType(BelId bel, IdString pin) const { return bels.at(bel).pins.at(pin).type; } +PortType Arch::getBelPinType(BelId bel, IdString pin) const { return bel_info(bel).pins.at(pin).type; } std::vector<IdString> Arch::getBelPins(BelId bel) const { std::vector<IdString> ret; - for (auto &it : bels.at(bel).pins) + for (auto &it : bel_info(bel).pins) ret.push_back(it.first); return ret; } @@ -356,26 +349,27 @@ const std::vector<IdString> &Arch::getBelPinsForCellPin(const CellInfo *cell_inf WireId Arch::getWireByName(IdStringList name) const { - if (wires.count(name)) - return name; - return WireId(); + if (name.size() == 0) + return WireId(); + auto fnd = wire_by_name.find(name); + if (fnd == wire_by_name.end()) + NPNR_ASSERT_FALSE_STR("no wire named " + name.str(getCtx())); + return fnd->second; } -IdStringList Arch::getWireName(WireId wire) const { return wire; } +IdStringList Arch::getWireName(WireId wire) const { return wire_info(wire).name; } -IdString Arch::getWireType(WireId wire) const { return wires.at(wire).type; } +IdString Arch::getWireType(WireId wire) const { return wire_info(wire).type; } -const std::map<IdString, std::string> &Arch::getWireAttrs(WireId wire) const { return wires.at(wire).attrs; } +const std::map<IdString, std::string> &Arch::getWireAttrs(WireId wire) const { return wire_info(wire).attrs; } -uint32_t Arch::getWireChecksum(WireId wire) const -{ - // FIXME - return 0; -} +uint32_t Arch::getWireChecksum(WireId wire) const { return wire.index; } void Arch::bindWire(WireId wire, NetInfo *net, PlaceStrength strength) { - wires.at(wire).bound_net = net; + if (uarch) + uarch->notifyWireChange(wire, net); + wire_info(wire).bound_net = net; net->wires[wire].pip = PipId(); net->wires[wire].strength = strength; refreshUiWire(wire); @@ -383,55 +377,64 @@ void Arch::bindWire(WireId wire, NetInfo *net, PlaceStrength strength) void Arch::unbindWire(WireId wire) { - auto &net_wires = wires.at(wire).bound_net->wires; + auto &net_wires = wire_info(wire).bound_net->wires; auto pip = net_wires.at(wire).pip; if (pip != PipId()) { - pips.at(pip).bound_net = nullptr; + if (uarch) + uarch->notifyPipChange(pip, nullptr); + pip_info(pip).bound_net = nullptr; refreshUiPip(pip); } + uarch->notifyWireChange(wire, nullptr); net_wires.erase(wire); - wires.at(wire).bound_net = nullptr; + wire_info(wire).bound_net = nullptr; refreshUiWire(wire); } -bool Arch::checkWireAvail(WireId wire) const { return wires.at(wire).bound_net == nullptr; } +bool Arch::checkWireAvail(WireId wire) const +{ + return (!uarch || uarch->checkWireAvail(wire)) && (wire_info(wire).bound_net == nullptr); +} -NetInfo *Arch::getBoundWireNet(WireId wire) const { return wires.at(wire).bound_net; } +NetInfo *Arch::getBoundWireNet(WireId wire) const { return wire_info(wire).bound_net; } -NetInfo *Arch::getConflictingWireNet(WireId wire) const { return wires.at(wire).bound_net; } +NetInfo *Arch::getConflictingWireNet(WireId wire) const { return wire_info(wire).bound_net; } -const std::vector<BelPin> &Arch::getWireBelPins(WireId wire) const { return wires.at(wire).bel_pins; } +const std::vector<BelPin> &Arch::getWireBelPins(WireId wire) const { return wire_info(wire).bel_pins; } -const std::vector<WireId> &Arch::getWires() const { return wire_ids; } +linear_range<WireId> Arch::getWires() const { return linear_range<WireId>(wires.size()); } // --------------------------------------------------------------- PipId Arch::getPipByName(IdStringList name) const { - if (pips.count(name)) - return name; - return PipId(); + if (name.size() == 0) + return PipId(); + auto fnd = pip_by_name.find(name); + if (fnd == pip_by_name.end()) + NPNR_ASSERT_FALSE_STR("no pip named " + name.str(getCtx())); + return fnd->second; } -IdStringList Arch::getPipName(PipId pip) const { return pip; } +IdStringList Arch::getPipName(PipId pip) const { return pip_info(pip).name; } -IdString Arch::getPipType(PipId pip) const { return pips.at(pip).type; } +IdString Arch::getPipType(PipId pip) const { return pip_info(pip).type; } -const std::map<IdString, std::string> &Arch::getPipAttrs(PipId pip) const { return pips.at(pip).attrs; } +const std::map<IdString, std::string> &Arch::getPipAttrs(PipId pip) const { return pip_info(pip).attrs; } -uint32_t Arch::getPipChecksum(PipId wire) const -{ - // FIXME - return 0; -} +uint32_t Arch::getPipChecksum(PipId pip) const { return pip.index; } void Arch::bindPip(PipId pip, NetInfo *net, PlaceStrength strength) { - WireId wire = pips.at(pip).dstWire; - pips.at(pip).bound_net = net; - wires.at(wire).bound_net = net; + WireId wire = pip_info(pip).dstWire; + if (uarch) { + uarch->notifyPipChange(pip, net); + uarch->notifyWireChange(wire, net); + } + pip_info(pip).bound_net = net; + wire_info(wire).bound_net = net; net->wires[wire].pip = pip; net->wires[wire].strength = strength; refreshUiPip(pip); @@ -440,41 +443,53 @@ void Arch::bindPip(PipId pip, NetInfo *net, PlaceStrength strength) void Arch::unbindPip(PipId pip) { - WireId wire = pips.at(pip).dstWire; - wires.at(wire).bound_net->wires.erase(wire); - pips.at(pip).bound_net = nullptr; - wires.at(wire).bound_net = nullptr; + WireId wire = pip_info(pip).dstWire; + if (uarch) { + uarch->notifyPipChange(pip, nullptr); + uarch->notifyWireChange(wire, nullptr); + } + wire_info(wire).bound_net->wires.erase(wire); + pip_info(pip).bound_net = nullptr; + wire_info(wire).bound_net = nullptr; refreshUiPip(pip); refreshUiWire(wire); } -bool Arch::checkPipAvail(PipId pip) const { return pips.at(pip).bound_net == nullptr; } +bool Arch::checkPipAvail(PipId pip) const +{ + return (!uarch || uarch->checkPipAvail(pip)) && (pip_info(pip).bound_net == nullptr); +} bool Arch::checkPipAvailForNet(PipId pip, NetInfo *net) const { - NetInfo *bound_net = pips.at(pip).bound_net; + if (uarch && !uarch->checkPipAvailForNet(pip, net)) + return false; + NetInfo *bound_net = pip_info(pip).bound_net; return bound_net == nullptr || bound_net == net; } -NetInfo *Arch::getBoundPipNet(PipId pip) const { return pips.at(pip).bound_net; } +NetInfo *Arch::getBoundPipNet(PipId pip) const { return pip_info(pip).bound_net; } -NetInfo *Arch::getConflictingPipNet(PipId pip) const { return pips.at(pip).bound_net; } +NetInfo *Arch::getConflictingPipNet(PipId pip) const { return pip_info(pip).bound_net; } -WireId Arch::getConflictingPipWire(PipId pip) const { return pips.at(pip).bound_net ? pips.at(pip).dstWire : WireId(); } +WireId Arch::getConflictingPipWire(PipId pip) const +{ + return pip_info(pip).bound_net ? pip_info(pip).dstWire : WireId(); +} -const std::vector<PipId> &Arch::getPips() const { return pip_ids; } +linear_range<PipId> Arch::getPips() const { return linear_range<PipId>(pips.size()); } -Loc Arch::getPipLocation(PipId pip) const { return pips.at(pip).loc; } +Loc Arch::getPipLocation(PipId pip) const { return pip_info(pip).loc; } -WireId Arch::getPipSrcWire(PipId pip) const { return pips.at(pip).srcWire; } +WireId Arch::getPipSrcWire(PipId pip) const { return pip_info(pip).srcWire; } -WireId Arch::getPipDstWire(PipId pip) const { return pips.at(pip).dstWire; } +WireId Arch::getPipDstWire(PipId pip) const { return pip_info(pip).dstWire; } -DelayQuad Arch::getPipDelay(PipId pip) const { return DelayQuad(pips.at(pip).delay); } +DelayQuad Arch::getPipDelay(PipId pip) const { return DelayQuad(pip_info(pip).delay); } -const std::vector<PipId> &Arch::getPipsDownhill(WireId wire) const { return wires.at(wire).downhill; } +const std::vector<PipId> &Arch::getPipsDownhill(WireId wire) const { return wire_info(wire).downhill; } -const std::vector<PipId> &Arch::getPipsUphill(WireId wire) const { return wires.at(wire).uphill; } +const std::vector<PipId> &Arch::getPipsUphill(WireId wire) const { return wire_info(wire).uphill; } // --------------------------------------------------------------- @@ -502,18 +517,21 @@ const std::vector<GroupId> &Arch::getGroupGroups(GroupId group) const { return g delay_t Arch::estimateDelay(WireId src, WireId dst) const { - const WireInfo &s = wires.at(src); - const WireInfo &d = wires.at(dst); + if (uarch) + return uarch->estimateDelay(src, dst); + const WireInfo &s = wire_info(src); + const WireInfo &d = wire_info(dst); int dx = abs(s.x - d.x); int dy = abs(s.y - d.y); return (dx + dy) * args.delayScale + args.delayOffset; } -delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const { - const auto &driver = net_info->driver; - auto driver_loc = getBelLocation(driver.cell->bel); - auto sink_loc = getBelLocation(sink.cell->bel); + if (uarch) + return uarch->predictDelay(src_bel, src_pin, dst_bel, dst_pin); + auto driver_loc = getBelLocation(src_bel); + auto sink_loc = getBelLocation(dst_bel); int dx = abs(sink_loc.x - driver_loc.x); int dy = abs(sink_loc.y - driver_loc.y); @@ -524,12 +542,14 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const { + if (uarch) + return uarch->getRouteBoundingBox(src, dst); ArcBounds bb; - int src_x = wires.at(src).x; - int src_y = wires.at(src).y; - int dst_x = wires.at(dst).x; - int dst_y = wires.at(dst).y; + int src_x = wire_info(src).x; + int src_y = wire_info(src).y; + int dst_x = wire_info(dst).x; + int dst_y = wire_info(dst).y; bb.x0 = src_x; bb.y0 = src_y; @@ -550,6 +570,8 @@ ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const bool Arch::place() { + if (uarch) + uarch->prePlace(); std::string placer = str_or_default(settings, id("placer"), defaultPlacer); if (placer == "heap") { bool have_iobuf_or_constr = false; @@ -561,7 +583,7 @@ bool Arch::place() } } bool retVal; - if (!have_iobuf_or_constr) { + if (!have_iobuf_or_constr && !uarch) { log_warning("Unable to use HeAP due to a lack of IO buffers or constrained cells as anchors; reverting to " "SA.\n"); retVal = placer1(getCtx(), Placer1Cfg(getCtx())); @@ -570,11 +592,15 @@ bool Arch::place() cfg.ioBufTypes.insert(id("GENERIC_IOB")); retVal = placer_heap(getCtx(), cfg); } + if (uarch) + uarch->postPlace(); getCtx()->settings[getCtx()->id("place")] = 1; archInfoToAttributes(); return retVal; } else if (placer == "sa") { bool retVal = placer1(getCtx(), Placer1Cfg(getCtx())); + if (uarch) + uarch->postPlace(); getCtx()->settings[getCtx()->id("place")] = 1; archInfoToAttributes(); return retVal; @@ -585,6 +611,8 @@ bool Arch::place() bool Arch::route() { + if (uarch) + uarch->preRoute(); std::string router = str_or_default(settings, id("router"), defaultRouter); bool result; if (router == "router1") { @@ -595,6 +623,8 @@ bool Arch::route() } else { log_error("iCE40 architecture does not support router '%s'\n", router.c_str()); } + if (uarch) + uarch->postRoute(); getCtx()->settings[getCtx()->id("route")] = 1; archInfoToAttributes(); return result; @@ -610,11 +640,11 @@ const std::vector<GraphicElement> &Arch::getDecalGraphics(DecalId decal) const return decal_graphics.at(decal); } -DecalXY Arch::getBelDecal(BelId bel) const { return bels.at(bel).decalxy; } +DecalXY Arch::getBelDecal(BelId bel) const { return bel_info(bel).decalxy; } -DecalXY Arch::getWireDecal(WireId wire) const { return wires.at(wire).decalxy; } +DecalXY Arch::getWireDecal(WireId wire) const { return wire_info(wire).decalxy; } -DecalXY Arch::getPipDecal(PipId pip) const { return pips.at(pip).decalxy; } +DecalXY Arch::getPipDecal(PipId pip) const { return pip_info(pip).decalxy; } DecalXY Arch::getGroupDecal(GroupId group) const { return groups.at(group).decalxy; } @@ -657,6 +687,8 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port bool Arch::isBelLocationValid(BelId bel) const { + if (uarch) + return uarch->isBelLocationValid(bel); std::vector<const CellInfo *> cells; Loc loc = getBelLocation(bel); for (auto tbel : getBelsByTile(loc.x, loc.y)) { @@ -684,6 +716,7 @@ const std::vector<std::string> Arch::availableRouters = {"router1", "router2"}; void Arch::assignArchInfo() { + int index = 0; for (auto &cell : getCtx()->cells) { CellInfo *ci = cell.second.get(); if (ci->type == id("GENERIC_SLICE")) { @@ -697,6 +730,8 @@ void Arch::assignArchInfo() for (auto &p : ci->ports) if (!ci->bel_pins.count(p.first)) ci->bel_pins.emplace(p.first, std::vector<IdString>{p.first}); + ci->flat_index = index; + ++index; } } diff --git a/generic/arch.h b/generic/arch.h index 2344d8b2..e96853f1 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -23,10 +23,12 @@ #include <map> #include "arch_api.h" +#include "base_arch.h" #include "idstring.h" #include "idstringlist.h" #include "nextpnr_namespaces.h" #include "nextpnr_types.h" +#include "viaduct_api.h" NEXTPNR_NAMESPACE_BEGIN @@ -60,8 +62,6 @@ struct WireInfo std::map<IdString, std::string> attrs; NetInfo *bound_net; std::vector<PipId> downhill, uphill; - BelPin uphill_bel_pin; - std::vector<BelPin> downhill_bel_pins; std::vector<BelPin> bel_pins; DecalXY decalxy; int x, y; @@ -111,23 +111,40 @@ struct CellTiming dict<IdString, std::vector<TimingClockingInfo>> clockingInfo; }; -struct ArchRanges +template <typename TId> struct linear_range +{ + struct iterator + { + explicit iterator(int32_t index) : index(index){}; + int32_t index; + bool operator==(const iterator &other) const { return index == other.index; } + bool operator!=(const iterator &other) const { return index != other.index; } + void operator++() { ++index; } + TId operator*() const { return TId(index); } + }; + explicit linear_range(int32_t size) : size(size){}; + int32_t size; + iterator begin() const { return iterator(0); } + iterator end() const { return iterator(size); } +}; + +struct ArchRanges : BaseArchRanges { using ArchArgsT = ArchArgs; // Bels - using AllBelsRangeT = const std::vector<BelId> &; + using AllBelsRangeT = linear_range<BelId>; using TileBelsRangeT = const std::vector<BelId> &; using BelAttrsRangeT = const std::map<IdString, std::string> &; using BelPinsRangeT = std::vector<IdString>; using CellBelPinRangeT = const std::vector<IdString> &; // Wires - using AllWiresRangeT = const std::vector<WireId> &; + using AllWiresRangeT = linear_range<WireId>; using DownhillPipRangeT = const std::vector<PipId> &; using UphillPipRangeT = const std::vector<PipId> &; using WireBelPinRangeT = const std::vector<BelPin> &; using WireAttrsRangeT = const std::map<IdString, std::string> &; // Pips - using AllPipsRangeT = const std::vector<PipId> &; + using AllPipsRangeT = linear_range<PipId>; using PipAttrsRangeT = const std::map<IdString, std::string> &; // Groups using AllGroupsRangeT = std::vector<GroupId>; @@ -143,21 +160,27 @@ struct ArchRanges using BucketBelRangeT = std::vector<BelId>; }; -struct Arch : ArchAPI<ArchRanges> +struct Arch : BaseArch<ArchRanges> { std::string chipName; + std::unique_ptr<ViaductAPI> uarch{}; - dict<IdStringList, WireInfo> wires; - dict<IdStringList, PipInfo> pips; - dict<IdStringList, BelInfo> bels; + std::vector<WireInfo> wires; + std::vector<PipInfo> pips; + std::vector<BelInfo> bels; dict<GroupId, GroupInfo> groups; - // These functions include useful errors if not found - WireInfo &wire_info(IdStringList wire); - PipInfo &pip_info(IdStringList wire); - BelInfo &bel_info(IdStringList wire); + WireInfo &wire_info(WireId wire) { return wires.at(wire.index); } + PipInfo &pip_info(PipId pip) { return pips.at(pip.index); } + BelInfo &bel_info(BelId bel) { return bels.at(bel.index); } + + const WireInfo &wire_info(WireId wire) const { return wires.at(wire.index); } + const PipInfo &pip_info(PipId pip) const { return pips.at(pip.index); } + const BelInfo &bel_info(BelId bel) const { return bels.at(bel.index); } - std::vector<IdStringList> bel_ids, wire_ids, pip_ids; + dict<IdStringList, WireId> wire_by_name; + dict<IdStringList, PipId> pip_by_name; + dict<IdStringList, BelId> bel_by_name; dict<Loc, BelId> bel_by_loc; std::vector<std::vector<std::vector<BelId>>> bels_by_tile; @@ -170,17 +193,17 @@ struct Arch : ArchAPI<ArchRanges> dict<IdString, CellTiming> cellTiming; - void addWire(IdStringList name, IdString type, int x, int y); - void addPip(IdStringList name, IdString type, IdStringList srcWire, IdStringList dstWire, delay_t delay, Loc loc); + WireId addWire(IdStringList name, IdString type, int x, int y); + PipId addPip(IdStringList name, IdString type, WireId srcWire, WireId dstWire, delay_t delay, Loc loc); - void addBel(IdStringList name, IdString type, Loc loc, bool gb, bool hidden); - void addBelInput(IdStringList bel, IdString name, IdStringList wire); - void addBelOutput(IdStringList bel, IdString name, IdStringList wire); - void addBelInout(IdStringList bel, IdString name, IdStringList wire); + BelId addBel(IdStringList name, IdString type, Loc loc, bool gb, bool hidden); + void addBelInput(BelId bel, IdString name, WireId wire); + void addBelOutput(BelId bel, IdString name, WireId wire); + void addBelInout(BelId bel, IdString name, WireId wire); - void addGroupBel(IdStringList group, IdStringList bel); - void addGroupWire(IdStringList group, IdStringList wire); - void addGroupPip(IdStringList group, IdStringList pip); + void addGroupBel(IdStringList group, BelId bel); + void addGroupWire(IdStringList group, WireId wire); + void addGroupPip(IdStringList group, PipId pip); void addGroupGroup(IdStringList group, IdStringList grp); void addDecalGraphic(DecalId decal, const GraphicElement &graphic); @@ -189,9 +212,9 @@ struct Arch : ArchAPI<ArchRanges> void setBelDecal(BelId bel, DecalXY decalxy); void setGroupDecal(GroupId group, DecalXY decalxy); - void setWireAttr(IdStringList wire, IdString key, const std::string &value); - void setPipAttr(IdStringList pip, IdString key, const std::string &value); - void setBelAttr(IdStringList bel, IdString key, const std::string &value); + void setWireAttr(WireId wire, IdString key, const std::string &value); + void setPipAttr(PipId pip, IdString key, const std::string &value); + void setBelAttr(BelId bel, IdString key, const std::string &value); void setLutK(int K); void setDelayScaling(double scale, double offset); @@ -234,7 +257,7 @@ struct Arch : ArchAPI<ArchRanges> bool checkBelAvail(BelId bel) const override; CellInfo *getBoundBelCell(BelId bel) const override; CellInfo *getConflictingBelCell(BelId bel) const override; - const std::vector<BelId> &getBels() const override; + linear_range<BelId> getBels() const override; IdString getBelType(BelId bel) const override; bool getBelHidden(BelId bel) const override; const std::map<IdString, std::string> &getBelAttrs(BelId bel) const override; @@ -255,7 +278,7 @@ struct Arch : ArchAPI<ArchRanges> WireId getConflictingWireWire(WireId wire) const override { return wire; } NetInfo *getConflictingWireNet(WireId wire) const override; DelayQuad getWireDelay(WireId wire) const override { return DelayQuad(0); } - const std::vector<WireId> &getWires() const override; + linear_range<WireId> getWires() const override; const std::vector<BelPin> &getWireBelPins(WireId wire) const override; PipId getPipByName(IdStringList name) const override; @@ -270,7 +293,7 @@ struct Arch : ArchAPI<ArchRanges> NetInfo *getBoundPipNet(PipId pip) const override; WireId getConflictingPipWire(PipId pip) const override; NetInfo *getConflictingPipNet(PipId pip) const override; - const std::vector<PipId> &getPips() const override; + linear_range<PipId> getPips() const override; Loc getPipLocation(PipId pip) const override; WireId getPipSrcWire(PipId pip) const override; WireId getPipDstWire(PipId pip) const override; @@ -287,7 +310,7 @@ struct Arch : ArchAPI<ArchRanges> const std::vector<GroupId> &getGroupGroups(GroupId group) const override; delay_t estimateDelay(WireId src, WireId dst) const override; - delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override; + delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const override; delay_t getDelayEpsilon() const override { return 0.001; } delay_t getRipupDelayPenalty() const override { return 0.015; } float getDelayNS(delay_t v) const override { return v; } @@ -305,9 +328,11 @@ struct Arch : ArchAPI<ArchRanges> std::vector<IdString> getCellTypes() const override { + if (uarch) + return uarch->getCellTypes(); pool<IdString> cell_types; for (auto bel : bels) { - cell_types.insert(bel.second.type); + cell_types.insert(bel.type); } return std::vector<IdString>{cell_types.begin(), cell_types.end()}; @@ -319,9 +344,15 @@ struct Arch : ArchAPI<ArchRanges> BelBucketId getBelBucketByName(IdString bucket) const override { return bucket; } - BelBucketId getBelBucketForBel(BelId bel) const override { return getBelType(bel); } + BelBucketId getBelBucketForBel(BelId bel) const override + { + return uarch ? uarch->getBelBucketForBel(bel) : getBelType(bel); + } - BelBucketId getBelBucketForCellType(IdString cell_type) const override { return cell_type; } + BelBucketId getBelBucketForCellType(IdString cell_type) const override + { + return uarch ? uarch->getBelBucketForCellType(cell_type) : cell_type; + } std::vector<BelId> getBelsInBucket(BelBucketId bucket) const override { @@ -346,19 +377,11 @@ struct Arch : ArchAPI<ArchRanges> // Get the TimingClockingInfo of a port TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const override; - bool isValidBelForCellType(IdString cell_type, BelId bel) const override { return cell_type == getBelType(bel); } - bool isBelLocationValid(BelId bel) const override; - - // TODO - CellInfo *getClusterRootCell(ClusterId cluster) const override { NPNR_ASSERT_FALSE("unimplemented"); } - ArcBounds getClusterBounds(ClusterId cluster) const override { NPNR_ASSERT_FALSE("unimplemented"); } - Loc getClusterOffset(const CellInfo *cell) const override { NPNR_ASSERT_FALSE("unimplemented"); } - bool isClusterStrict(const CellInfo *cell) const override { NPNR_ASSERT_FALSE("unimplemented"); } - bool getClusterPlacement(ClusterId cluster, BelId root_bel, - std::vector<std::pair<CellInfo *, BelId>> &placement) const override + bool isValidBelForCellType(IdString cell_type, BelId bel) const override { - NPNR_ASSERT_FALSE("unimplemented"); + return uarch ? uarch->isValidBelForCellType(cell_type, bel) : cell_type == getBelType(bel); } + bool isBelLocationValid(BelId bel) const override; static const std::string defaultPlacer; static const std::vector<std::string> availablePlacers; diff --git a/generic/arch_pybindings.cc b/generic/arch_pybindings.cc index df59b4fe..92c78252 100644 --- a/generic/arch_pybindings.cc +++ b/generic/arch_pybindings.cc @@ -42,6 +42,10 @@ void arch_wrap_python(py::module &m) { using namespace PythonConversion; + typedef linear_range<BelId> BelRange; + typedef linear_range<WireId> WireRange; + typedef linear_range<PipId> AllPipRange; + auto arch_cls = py::class_<Arch, BaseCtx>(m, "Arch").def(py::init<ArchArgs>()); auto dxy_cls = py::class_<ContextualWrapper<DecalXY>>(m, "DecalXY_"); @@ -74,8 +78,8 @@ void arch_wrap_python(py::module &m) conv_from_str<BelId>>::def_wrap(ctx_cls, "getBoundBelCell"); fn_wrapper_1a<Context, decltype(&Context::getConflictingBelCell), &Context::getConflictingBelCell, deref_and_wrap<CellInfo>, conv_from_str<BelId>>::def_wrap(ctx_cls, "getConflictingBelCell"); - fn_wrapper_0a<Context, decltype(&Context::getBels), &Context::getBels, - wrap_context<const std::vector<BelId> &>>::def_wrap(ctx_cls, "getBels"); + fn_wrapper_0a<Context, decltype(&Context::getBels), &Context::getBels, wrap_context<BelRange>>::def_wrap(ctx_cls, + "getBels"); fn_wrapper_2a<Context, decltype(&Context::getBelPinWire), &Context::getBelPinWire, conv_to_str<WireId>, conv_from_str<BelId>, conv_from_str<IdString>>::def_wrap(ctx_cls, "getBelPinWire"); @@ -96,11 +100,11 @@ void arch_wrap_python(py::module &m) fn_wrapper_1a<Context, decltype(&Context::getConflictingWireNet), &Context::getConflictingWireNet, deref_and_wrap<NetInfo>, conv_from_str<WireId>>::def_wrap(ctx_cls, "getConflictingWireNet"); - fn_wrapper_0a<Context, decltype(&Context::getWires), &Context::getWires, - wrap_context<const std::vector<WireId> &>>::def_wrap(ctx_cls, "getWires"); + fn_wrapper_0a<Context, decltype(&Context::getWires), &Context::getWires, wrap_context<WireRange>>::def_wrap( + ctx_cls, "getWires"); - fn_wrapper_0a<Context, decltype(&Context::getPips), &Context::getPips, - wrap_context<const std::vector<PipId> &>>::def_wrap(ctx_cls, "getPips"); + fn_wrapper_0a<Context, decltype(&Context::getPips), &Context::getPips, wrap_context<AllPipRange>>::def_wrap( + ctx_cls, "getPips"); fn_wrapper_1a<Context, decltype(&Context::getPipChecksum), &Context::getPipChecksum, pass_through<uint32_t>, conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipChecksum"); fn_wrapper_3a_v<Context, decltype(&Context::bindPip), &Context::bindPip, conv_from_str<PipId>, @@ -156,50 +160,50 @@ void arch_wrap_python(py::module &m) "name"_a, "type"_a, "x"_a, "y"_a); fn_wrapper_6a_v<Context, decltype(&Context::addPip), &Context::addPip, conv_from_str<IdStringList>, - conv_from_str<IdString>, conv_from_str<IdStringList>, conv_from_str<IdStringList>, - pass_through<delay_t>, pass_through<Loc>>::def_wrap(ctx_cls, "addPip", "name"_a, "type"_a, - "srcWire"_a, "dstWire"_a, "delay"_a, "loc"_a); + conv_from_str<IdString>, conv_from_str<WireId>, conv_from_str<WireId>, pass_through<delay_t>, + pass_through<Loc>>::def_wrap(ctx_cls, "addPip", "name"_a, "type"_a, "srcWire"_a, "dstWire"_a, + "delay"_a, "loc"_a); fn_wrapper_5a_v<Context, decltype(&Context::addBel), &Context::addBel, conv_from_str<IdStringList>, conv_from_str<IdString>, pass_through<Loc>, pass_through<bool>, pass_through<bool>>::def_wrap(ctx_cls, "addBel", "name"_a, "type"_a, "loc"_a, "gb"_a, "hidden"_a); - fn_wrapper_3a_v<Context, decltype(&Context::addBelInput), &Context::addBelInput, conv_from_str<IdStringList>, - conv_from_str<IdString>, conv_from_str<IdStringList>>::def_wrap(ctx_cls, "addBelInput", "bel"_a, - "name"_a, "wire"_a); - fn_wrapper_3a_v<Context, decltype(&Context::addBelOutput), &Context::addBelOutput, conv_from_str<IdStringList>, - conv_from_str<IdString>, conv_from_str<IdStringList>>::def_wrap(ctx_cls, "addBelOutput", "bel"_a, - "name"_a, "wire"_a); - fn_wrapper_3a_v<Context, decltype(&Context::addBelInout), &Context::addBelInout, conv_from_str<IdStringList>, - conv_from_str<IdString>, conv_from_str<IdStringList>>::def_wrap(ctx_cls, "addBelInout", "bel"_a, - "name"_a, "wire"_a); + fn_wrapper_3a_v<Context, decltype(&Context::addBelInput), &Context::addBelInput, conv_from_str<BelId>, + conv_from_str<IdString>, conv_from_str<WireId>>::def_wrap(ctx_cls, "addBelInput", "bel"_a, "name"_a, + "wire"_a); + fn_wrapper_3a_v<Context, decltype(&Context::addBelOutput), &Context::addBelOutput, conv_from_str<BelId>, + conv_from_str<IdString>, conv_from_str<WireId>>::def_wrap(ctx_cls, "addBelOutput", "bel"_a, + "name"_a, "wire"_a); + fn_wrapper_3a_v<Context, decltype(&Context::addBelInout), &Context::addBelInout, conv_from_str<BelId>, + conv_from_str<IdString>, conv_from_str<WireId>>::def_wrap(ctx_cls, "addBelInout", "bel"_a, "name"_a, + "wire"_a); fn_wrapper_2a_v<Context, decltype(&Context::addGroupBel), &Context::addGroupBel, conv_from_str<IdStringList>, - conv_from_str<IdStringList>>::def_wrap(ctx_cls, "addGroupBel", "group"_a, "bel"_a); + conv_from_str<BelId>>::def_wrap(ctx_cls, "addGroupBel", "group"_a, "bel"_a); fn_wrapper_2a_v<Context, decltype(&Context::addGroupWire), &Context::addGroupWire, conv_from_str<IdStringList>, - conv_from_str<IdStringList>>::def_wrap(ctx_cls, "addGroupWire", "group"_a, "wire"_a); + conv_from_str<WireId>>::def_wrap(ctx_cls, "addGroupWire", "group"_a, "wire"_a); fn_wrapper_2a_v<Context, decltype(&Context::addGroupPip), &Context::addGroupPip, conv_from_str<IdStringList>, - conv_from_str<IdStringList>>::def_wrap(ctx_cls, "addGroupPip", "group"_a, "pip"_a); - fn_wrapper_2a_v<Context, decltype(&Context::addGroupGroup), &Context::addGroupPip, conv_from_str<IdStringList>, + conv_from_str<PipId>>::def_wrap(ctx_cls, "addGroupPip", "group"_a, "pip"_a); + fn_wrapper_2a_v<Context, decltype(&Context::addGroupGroup), &Context::addGroupGroup, conv_from_str<IdStringList>, conv_from_str<IdStringList>>::def_wrap(ctx_cls, "addGroupGroup", "group"_a, "grp"_a); fn_wrapper_2a_v<Context, decltype(&Context::addDecalGraphic), &Context::addDecalGraphic, conv_from_str<DecalId>, pass_through<GraphicElement>>::def_wrap(ctx_cls, "addDecalGraphic", (py::arg("decal"), "graphic")); - fn_wrapper_2a_v<Context, decltype(&Context::setWireDecal), &Context::setWireDecal, conv_from_str<DecalId>, + fn_wrapper_2a_v<Context, decltype(&Context::setWireDecal), &Context::setWireDecal, conv_from_str<WireId>, unwrap_context<DecalXY>>::def_wrap(ctx_cls, "setWireDecal", "wire"_a, "decalxy"_a); - fn_wrapper_2a_v<Context, decltype(&Context::setPipDecal), &Context::setPipDecal, conv_from_str<DecalId>, + fn_wrapper_2a_v<Context, decltype(&Context::setPipDecal), &Context::setPipDecal, conv_from_str<PipId>, unwrap_context<DecalXY>>::def_wrap(ctx_cls, "setPipDecal", "pip"_a, "decalxy"_a); - fn_wrapper_2a_v<Context, decltype(&Context::setBelDecal), &Context::setBelDecal, conv_from_str<DecalId>, + fn_wrapper_2a_v<Context, decltype(&Context::setBelDecal), &Context::setBelDecal, conv_from_str<BelId>, unwrap_context<DecalXY>>::def_wrap(ctx_cls, "setBelDecal", "bel"_a, "decalxy"_a); fn_wrapper_2a_v<Context, decltype(&Context::setGroupDecal), &Context::setGroupDecal, conv_from_str<DecalId>, unwrap_context<DecalXY>>::def_wrap(ctx_cls, "setGroupDecal", "group"_a, "decalxy"_a); - fn_wrapper_3a_v<Context, decltype(&Context::setWireAttr), &Context::setWireAttr, conv_from_str<DecalId>, + fn_wrapper_3a_v<Context, decltype(&Context::setWireAttr), &Context::setWireAttr, conv_from_str<WireId>, conv_from_str<IdString>, pass_through<std::string>>::def_wrap(ctx_cls, "setWireAttr", "wire"_a, "key"_a, "value"_a); - fn_wrapper_3a_v<Context, decltype(&Context::setBelAttr), &Context::setBelAttr, conv_from_str<DecalId>, + fn_wrapper_3a_v<Context, decltype(&Context::setBelAttr), &Context::setBelAttr, conv_from_str<BelId>, conv_from_str<IdString>, pass_through<std::string>>::def_wrap(ctx_cls, "setBelAttr", "bel"_a, "key"_a, "value"_a); - fn_wrapper_3a_v<Context, decltype(&Context::setPipAttr), &Context::setPipAttr, conv_from_str<DecalId>, + fn_wrapper_3a_v<Context, decltype(&Context::setPipAttr), &Context::setPipAttr, conv_from_str<PipId>, conv_from_str<IdString>, pass_through<std::string>>::def_wrap(ctx_cls, "setPipAttr", "pip"_a, "key"_a, "value"_a); @@ -254,6 +258,10 @@ void arch_wrap_python(py::module &m) pass_through<bool>, conv_from_str<IdString>, conv_from_str<BelId>>::def_wrap(ctx_cls, "isValidBelForCellType"); + WRAP_RANGE(m, Bel, conv_to_str<BelId>); + WRAP_RANGE(m, Wire, conv_to_str<WireId>); + WRAP_RANGE(m, AllPip, conv_to_str<PipId>); + WRAP_MAP_UPTR(m, CellMap, "IdCellMap"); WRAP_MAP_UPTR(m, NetMap, "IdNetMap"); WRAP_MAP(m, HierarchyMap, wrap_context<HierarchicalCell &>, "HierarchyMap"); diff --git a/generic/arch_pybindings.h b/generic/arch_pybindings.h index 9a573540..72d688dc 100644 --- a/generic/arch_pybindings.h +++ b/generic/arch_pybindings.h @@ -26,6 +26,73 @@ NEXTPNR_NAMESPACE_BEGIN +namespace PythonConversion { + +template <> struct string_converter<BelId> +{ + BelId from_str(Context *ctx, std::string name) { return ctx->getBelByNameStr(name); } + + std::string to_str(Context *ctx, BelId id) + { + if (id == BelId()) + throw bad_wrap(); + return ctx->getBelName(id).str(ctx); + } +}; + +template <> struct string_converter<WireId> +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByNameStr(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<const WireId> +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByNameStr(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> +{ + PipId from_str(Context *ctx, std::string name) { return ctx->getPipByNameStr(name); } + + std::string to_str(Context *ctx, PipId id) + { + if (id == PipId()) + throw bad_wrap(); + return ctx->getPipName(id).str(ctx); + } +}; + +template <> struct string_converter<BelPin> +{ + BelPin from_str(Context *ctx, std::string name) + { + NPNR_ASSERT_FALSE("string_converter<BelPin>::from_str not implemented"); + } + + std::string to_str(Context *ctx, BelPin pin) + { + if (pin.bel == BelId()) + throw bad_wrap(); + return ctx->getBelName(pin.bel).str(ctx) + "/" + pin.pin.str(ctx); + } +}; + +} // namespace PythonConversion + NEXTPNR_NAMESPACE_END #endif #endif diff --git a/generic/archdefs.h b/generic/archdefs.h index c46fba93..4e91ffd3 100644 --- a/generic/archdefs.h +++ b/generic/archdefs.h @@ -20,6 +20,7 @@ #ifndef GENERIC_ARCHDEFS_H #define GENERIC_ARCHDEFS_H +#include "base_clusterinfo.h" #include "hashlib.h" #include "idstringlist.h" @@ -27,9 +28,42 @@ NEXTPNR_NAMESPACE_BEGIN typedef float delay_t; -typedef IdStringList BelId; -typedef IdStringList WireId; -typedef IdStringList PipId; +struct BelId +{ + BelId() : index(-1){}; + explicit BelId(int32_t index) : index(index){}; + int32_t index = -1; + + bool operator==(const BelId &other) const { return index == other.index; } + bool operator!=(const BelId &other) const { return index != other.index; } + bool operator<(const BelId &other) const { return index < other.index; } + unsigned int hash() const { return index; } +}; + +struct WireId +{ + WireId() : index(-1){}; + explicit WireId(int32_t index) : index(index){}; + int32_t index = -1; + + bool operator==(const WireId &other) const { return index == other.index; } + bool operator!=(const WireId &other) const { return index != other.index; } + bool operator<(const WireId &other) const { return index < other.index; } + unsigned int hash() const { return index; } +}; + +struct PipId +{ + PipId() : index(-1){}; + explicit PipId(int32_t index) : index(index){}; + int32_t index = -1; + + bool operator==(const PipId &other) const { return index == other.index; } + bool operator!=(const PipId &other) const { return index != other.index; } + bool operator<(const PipId &other) const { return index < other.index; } + unsigned int hash() const { return index; } +}; + typedef IdStringList GroupId; typedef IdStringList DecalId; typedef IdString BelBucketId; @@ -41,7 +75,7 @@ struct ArchNetInfo struct NetInfo; -struct ArchCellInfo +struct ArchCellInfo : BaseClusterInfo { // Custom grouping set via "PACK_GROUP" attribute. All cells with the same group // value may share a tile (-1 = don't care, default if not set) @@ -50,6 +84,8 @@ struct ArchCellInfo bool is_slice; // Only packing rule for slice type primitives is a single clock per tile const NetInfo *slice_clk; + // A flat index for cells; so viaduct uarches can have their own fast flat arrays of per-cell validity-related data + int flat_index; // Cell to bel pin mapping dict<IdString, std::vector<IdString>> bel_pins; }; diff --git a/generic/examples/write_fasm.py b/generic/examples/write_fasm.py index ede8f16b..057e779c 100644 --- a/generic/examples/write_fasm.py +++ b/generic/examples/write_fasm.py @@ -29,7 +29,7 @@ def write_fasm(ctx, paramCfg, f): for nname, net in sorted(ctx.nets, key=lambda x: str(x[1].name)): print("# Net %s" % nname, file=f) for wire, pip in sorted(net.wires, key=lambda x: str(x[1])): - if pip.pip != "": + if pip.pip is not None: print("%s" % pip.pip, file=f) print("", file=f) for cname, cell in sorted(ctx.cells, key=lambda x: str(x[1].name)): diff --git a/generic/family.cmake b/generic/family.cmake index e69de29b..cd4e3801 100644 --- a/generic/family.cmake +++ b/generic/family.cmake @@ -0,0 +1,7 @@ +set(VIADUCT_UARCHES "example") +foreach(uarch ${VIADUCT_UARCHES}) + aux_source_directory(${family}/viaduct/${uarch} UARCH_FILES) + foreach(target ${family_targets}) + target_sources(${target} PRIVATE ${UARCH_FILES}) + endforeach() +endforeach(uarch) diff --git a/generic/main.cc b/generic/main.cc index 387df6c6..d08ae381 100644 --- a/generic/main.cc +++ b/generic/main.cc @@ -44,8 +44,10 @@ GenericCommandHandler::GenericCommandHandler(int argc, char **argv) : CommandHan po::options_description GenericCommandHandler::getArchOptions() { + std::string all_uarches = ViaductArch::list(); + std::string uarch_help = stringf("viaduct micro-arch to use (available: %s)", all_uarches.c_str()); po::options_description specific("Architecture specific options"); - specific.add_options()("generic", "set device type to generic"); + specific.add_options()("uarch", po::value<std::string>(), uarch_help.c_str()); specific.add_options()("no-iobs", "disable automatic IO buffer insertion"); return specific; } @@ -63,6 +65,17 @@ std::unique_ptr<Context> GenericCommandHandler::createContext(dict<std::string, auto ctx = std::unique_ptr<Context>(new Context(chipArgs)); if (vm.count("no-iobs")) ctx->settings[ctx->id("disable_iobs")] = Property::State::S1; + if (vm.count("uarch")) { + std::string uarch_name = vm["uarch"].as<std::string>(); + dict<std::string, std::string> args; // TODO + auto uarch = ViaductArch::create(uarch_name, args); + if (!uarch) { + std::string all_uarches = ViaductArch::list(); + log_error("Unknown viaduct uarch '%s'; available options: '%s'\n", uarch_name.c_str(), all_uarches.c_str()); + } + ctx->uarch = std::move(uarch); + ctx->uarch->init(ctx.get()); + } return ctx; } diff --git a/generic/pack.cc b/generic/pack.cc index 32dae553..291a528d 100644 --- a/generic/pack.cc +++ b/generic/pack.cc @@ -276,12 +276,16 @@ bool Arch::pack() Context *ctx = getCtx(); try { log_break(); - pack_constants(ctx); - pack_io(ctx); - pack_lut_lutffs(ctx); - pack_nonlut_ffs(ctx); - ctx->settings[ctx->id("pack")] = 1; + if (uarch) { + uarch->pack(); + } else { + pack_constants(ctx); + pack_io(ctx); + pack_lut_lutffs(ctx); + pack_nonlut_ffs(ctx); + } ctx->assignArchInfo(); + ctx->settings[ctx->id("pack")] = 1; log_info("Checksum: 0x%08x\n", ctx->checksum()); return true; } catch (log_execution_error_exception) { diff --git a/generic/viaduct/example/.gitignore b/generic/viaduct/example/.gitignore new file mode 100644 index 00000000..a6c57f5f --- /dev/null +++ b/generic/viaduct/example/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/generic/viaduct/example/constids.inc b/generic/viaduct/example/constids.inc new file mode 100644 index 00000000..b40d5be8 --- /dev/null +++ b/generic/viaduct/example/constids.inc @@ -0,0 +1,14 @@ +X(LUT4) +X(DFF) +X(CLK) +X(D) +X(F) +X(Q) +X(INBUF) +X(OUTBUF) +X(I) +X(EN) +X(O) +X(IOB) +X(PAD) +X(INIT) diff --git a/generic/viaduct/example/example.cc b/generic/viaduct/example/example.cc new file mode 100644 index 00000000..3d1c201c --- /dev/null +++ b/generic/viaduct/example/example.cc @@ -0,0 +1,306 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@ds0.me> + * + * 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 "log.h" +#include "nextpnr.h" +#include "util.h" +#include "viaduct_api.h" +#include "viaduct_helpers.h" + +#define GEN_INIT_CONSTIDS +#define VIADUCT_CONSTIDS "viaduct/example/constids.inc" +#include "viaduct_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +struct ExampleImpl : ViaductAPI +{ + ~ExampleImpl(){}; + void init(Context *ctx) override + { + init_uarch_constids(ctx); + ViaductAPI::init(ctx); + h.init(ctx); + init_wires(); + init_bels(); + init_pips(); + } + + void pack() override + { + // Trim nextpnr IOBs - assume IO buffer insertion has been done in synthesis + const pool<CellTypePort> top_ports{ + CellTypePort(id_INBUF, id_PAD), + CellTypePort(id_OUTBUF, id_PAD), + }; + h.remove_nextpnr_iobs(top_ports); + // Replace constants with LUTs + const dict<IdString, Property> vcc_params = {{id_INIT, Property(0xFFFF, 16)}}; + const dict<IdString, Property> gnd_params = {{id_INIT, Property(0x0000, 16)}}; + h.replace_constants(CellTypePort(id_LUT4, id_F), CellTypePort(id_LUT4, id_F), vcc_params, gnd_params); + // Constrain directly connected LUTs and FFs together to use dedicated resources + int lutffs = h.constrain_cell_pairs(pool<CellTypePort>{{id_LUT4, id_F}}, pool<CellTypePort>{{id_DFF, id_D}}, 1); + log_info("Constrained %d LUTFF pairs.\n", lutffs); + } + + void prePlace() override { assign_cell_info(); } + + bool isBelLocationValid(BelId bel) const override + { + Loc l = ctx->getBelLocation(bel); + if (is_io(l.x, l.y)) { + return true; + } else { + return slice_valid(l.x, l.y, l.z / 2); + } + } + + private: + ViaductHelpers h; + // Configuration + // Grid size including IOBs at edges + const int X = 32, Y = 32; + // SLICEs per tile + const int N = 8; + // LUT input count + const int K = 4; + // Number of local wires + const int Wl = N * (K + 1) + 8; + // 1/Fc for bel input wire pips; local wire pips and neighbour pips + const int Si = 4, Sq = 4, Sl = 8; + + // For fast wire lookups + struct TileWires + { + std::vector<WireId> clk, q, f, d, i; + std::vector<WireId> l; + std::vector<WireId> pad; + }; + + std::vector<std::vector<TileWires>> wires_by_tile; + + // Create wires to attach to bels and pips + void init_wires() + { + log_info("Creating wires...\n"); + wires_by_tile.resize(Y); + for (int y = 0; y < Y; y++) { + auto &row_wires = wires_by_tile.at(y); + row_wires.resize(X); + for (int x = 0; x < X; x++) { + auto &w = row_wires.at(x); + for (int z = 0; z < N; z++) { + // Clock input + w.clk.push_back(ctx->addWire(h.xy_id(x, y, ctx->id(stringf("CLK%d", z))), ctx->id("CLK"), x, y)); + // FF input + w.d.push_back(ctx->addWire(h.xy_id(x, y, ctx->id(stringf("D%d", z))), ctx->id("D"), x, y)); + // FF and LUT outputs + w.q.push_back(ctx->addWire(h.xy_id(x, y, ctx->id(stringf("Q%d", z))), ctx->id("Q"), x, y)); + w.f.push_back(ctx->addWire(h.xy_id(x, y, ctx->id(stringf("F%d", z))), ctx->id("F"), x, y)); + // LUT inputs + for (int i = 0; i < K; i++) + w.i.push_back( + ctx->addWire(h.xy_id(x, y, ctx->id(stringf("L%dI%d", z, i))), ctx->id("I"), x, y)); + } + // Local wires + for (int l = 0; l < Wl; l++) + w.l.push_back(ctx->addWire(h.xy_id(x, y, ctx->id(stringf("LOCAL%d", l))), ctx->id("LOCAL"), x, y)); + // Pad wires for IO + if (is_io(x, y) && x != y) + for (int z = 0; z < 2; z++) + w.pad.push_back(ctx->addWire(h.xy_id(x, y, ctx->id(stringf("PAD%d", z))), id_PAD, x, y)); + } + } + } + bool is_io(int x, int y) const + { + // IO are on the edges of the device + return (x == 0) || (x == (X - 1)) || (y == 0) || (y == (Y - 1)); + } + // Create IO bels in an IO tile + void add_io_bels(int x, int y) + { + auto &w = wires_by_tile.at(y).at(x); + for (int z = 0; z < 2; z++) { + BelId b = ctx->addBel(h.xy_id(x, y, ctx->id(stringf("IO%d", z))), id_IOB, Loc(x, y, z), false, false); + ctx->addBelInout(b, id_PAD, w.pad.at(z)); + ctx->addBelInput(b, id_I, w.i.at(z * K + 0)); + ctx->addBelInput(b, id_EN, w.i.at(z * K + 1)); + ctx->addBelOutput(b, id_O, w.q.at(z)); + } + } + PipId add_pip(Loc loc, WireId src, WireId dst, delay_t delay = 0.05) + { + IdStringList name = IdStringList::concat(ctx->getWireName(dst), ctx->getWireName(src)); + return ctx->addPip(name, ctx->id("PIP"), src, dst, delay, loc); + } + // Create LUT and FF bels in a logic tile + void add_slice_bels(int x, int y) + { + auto &w = wires_by_tile.at(y).at(x); + for (int z = 0; z < N; z++) { + // Create LUT bel + BelId lut = ctx->addBel(h.xy_id(x, y, ctx->id(stringf("SLICE%d_LUT", z))), id_LUT4, Loc(x, y, z * 2), false, + false); + for (int k = 0; k < K; k++) + ctx->addBelInput(lut, ctx->id(stringf("I[%d]", k)), w.i.at(z * K + k)); + ctx->addBelOutput(lut, id_F, w.f.at(z)); + // FF data can come from LUT output or LUT I3 + add_pip(Loc(x, y, 0), w.f.at(z), w.d.at(z)); + add_pip(Loc(x, y, 0), w.i.at(z * K + (K - 1)), w.d.at(z)); + // Create DFF bel + BelId dff = ctx->addBel(h.xy_id(x, y, ctx->id(stringf("SLICE%d_FF", z))), id_DFF, Loc(x, y, z * 2 + 1), + false, false); + ctx->addBelInput(dff, id_CLK, w.clk.at(z)); + ctx->addBelInput(dff, id_D, w.d.at(z)); + ctx->addBelOutput(dff, id_Q, w.q.at(z)); + } + } + // Create bels according to tile type + void init_bels() + { + log_info("Creating bels...\n"); + for (int y = 0; y < Y; y++) { + for (int x = 0; x < X; x++) { + if (is_io(x, y)) { + if (x == y) + continue; // don't put IO in corners + add_io_bels(x, y); + } else { + add_slice_bels(x, y); + } + } + } + } + + // Create PIPs inside a tile; following an example synthetic routing pattern + void add_tile_pips(int x, int y) + { + auto &w = wires_by_tile.at(y).at(x); + Loc loc(x, y, 0); + auto create_input_pips = [&](WireId dst, int offset, int skip) { + for (int i = (offset % skip); i < Wl; i += skip) + add_pip(loc, w.l.at(i), dst, 0.05); + }; + for (int z = 0; z < N; z++) { + create_input_pips(w.clk.at(z), 0, Si); + for (int k = 0; k < K; k++) + create_input_pips(w.i.at(z * K + k), k, Si); + } + auto create_output_pips = [&](WireId dst, int offset, int skip) { + for (int z = (offset % skip); z < N; z += skip) { + add_pip(loc, w.f.at(z), dst, 0.05); + add_pip(loc, w.q.at(z), dst, 0.05); + } + }; + auto create_neighbour_pips = [&](WireId dst, int nx, int ny, int offset, int skip) { + if (nx < 0 || nx >= X) + return; + if (ny < 0 || ny >= Y) + return; + auto &nw = wires_by_tile.at(ny).at(nx); + for (int i = (offset % skip); i < Wl; i += skip) + add_pip(loc, dst, nw.l.at(i), 0.1); + }; + for (int i = 0; i < Wl; i++) { + WireId dst = w.l.at(i); + create_output_pips(dst, i % Sq, Sq); + create_neighbour_pips(dst, x - 1, y - 1, (i + 1) % Sl, Sl); + create_neighbour_pips(dst, x - 1, y, (i + 2) % Sl, Sl); + create_neighbour_pips(dst, x - 1, y + 1, (i + 3) % Sl, Sl); + create_neighbour_pips(dst, x, y - 1, (i + 4) % Sl, Sl); + create_neighbour_pips(dst, x, y + 1, (i + 5) % Sl, Sl); + create_neighbour_pips(dst, x + 1, y - 1, (i + 6) % Sl, Sl); + create_neighbour_pips(dst, x + 1, y, (i + 7) % Sl, Sl); + create_neighbour_pips(dst, x + 1, y + 1, (i + 8) % Sl, Sl); + } + } + void init_pips() + { + log_info("Creating pips...\n"); + for (int y = 0; y < Y; y++) + for (int x = 0; x < X; x++) + add_tile_pips(x, y); + } + // Validity checking + struct ExampleCellInfo + { + const NetInfo *lut_f = nullptr, *ff_d = nullptr; + bool lut_i3_used = false; + }; + std::vector<ExampleCellInfo> fast_cell_info; + void assign_cell_info() + { + fast_cell_info.resize(ctx->cells.size()); + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + auto &fc = fast_cell_info.at(ci->flat_index); + if (ci->type == id_LUT4) { + fc.lut_f = get_net_or_empty(ci, id_F); + fc.lut_i3_used = (get_net_or_empty(ci, ctx->id(stringf("I[%d]", K - 1))) != nullptr); + } else if (ci->type == id_DFF) { + fc.ff_d = get_net_or_empty(ci, id_D); + } + } + } + bool slice_valid(int x, int y, int z) const + { + const CellInfo *lut = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2))); + const CellInfo *ff = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2 + 1))); + if (!lut || !ff) + return true; // always valid if only LUT or FF used + const auto &lut_data = fast_cell_info.at(lut->flat_index); + const auto &ff_data = fast_cell_info.at(ff->flat_index); + // In our example arch; the FF D can either be driven from LUT F or LUT I3 + // so either; FF D must equal LUT F or LUT I3 must be unused + if (ff_data.ff_d == lut_data.lut_f) + return true; + if (lut_data.lut_i3_used) + return false; + return true; + } + // Bel bucket functions + IdString getBelBucketForCellType(IdString cell_type) const override + { + if (cell_type.in(id_INBUF, id_OUTBUF)) + return id_IOB; + return cell_type; + } + bool isValidBelForCellType(IdString cell_type, BelId bel) const override + { + IdString bel_type = ctx->getBelType(bel); + if (bel_type == id_IOB) + return cell_type.in(id_INBUF, id_OUTBUF); + else + return (bel_type == cell_type); + } +}; + +struct ExampleArch : ViaductArch +{ + ExampleArch() : ViaductArch("example"){}; + std::unique_ptr<ViaductAPI> create(const dict<std::string, std::string> &args) + { + return std::make_unique<ExampleImpl>(); + } +} exampleArch; +} // namespace + +NEXTPNR_NAMESPACE_END diff --git a/generic/viaduct/example/example_map.v b/generic/viaduct/example/example_map.v new file mode 100644 index 00000000..f701f0ed --- /dev/null +++ b/generic/viaduct/example/example_map.v @@ -0,0 +1,12 @@ +module \$lut (A, Y); + parameter WIDTH = 0; + parameter LUT = 0; + input [WIDTH-1:0] A; + output Y; + + localparam rep = 1<<(4-WIDTH); + + LUT4 #(.INIT({rep{LUT}})) _TECHMAP_REPLACE_ (.I(A), .F(Y)); +endmodule + +module \$_DFF_P_ (input D, C, output Q); DFF _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(C)); endmodule diff --git a/generic/viaduct/example/example_prims.v b/generic/viaduct/example/example_prims.v new file mode 100644 index 00000000..d2813129 --- /dev/null +++ b/generic/viaduct/example/example_prims.v @@ -0,0 +1,35 @@ +module LUT4 #( + parameter [15:0] INIT = 0 +) ( + input [3:0] I, + output F +); + wire [7:0] s3 = I[3] ? INIT[15:8] : INIT[7:0]; + wire [3:0] s2 = I[2] ? s3[ 7:4] : s3[3:0]; + wire [1:0] s1 = I[1] ? s2[ 3:2] : s2[1:0]; + assign F = I[0] ? s1[1] : s1[0]; +endmodule + +module DFF ( + input CLK, D, + output reg Q +); + initial Q = 1'b0; + always @(posedge CLK) + Q <= D; +endmodule + +module INBUF ( + input PAD, + output O, +); + assign O = PAD; +endmodule + +module OUTBUF ( + output PAD, + input I, +); + assign PAD = I; +endmodule + diff --git a/generic/viaduct/example/synth_viaduct_example.tcl b/generic/viaduct/example/synth_viaduct_example.tcl new file mode 100644 index 00000000..a9d18f56 --- /dev/null +++ b/generic/viaduct/example/synth_viaduct_example.tcl @@ -0,0 +1,24 @@ +# Usage +# tcl synth_viaduct_example.tcl {out.json} + +yosys read_verilog -lib [file dirname [file normalize $argv0]]/example_prims.v +yosys hierarchy -check +yosys proc +yosys flatten +yosys tribuf -logic +yosys deminout +yosys synth -run coarse +yosys memory_map +yosys opt -full +yosys iopadmap -bits -inpad INBUF O:PAD -outpad OUTBUF I:PAD +yosys techmap -map +/techmap.v +yosys opt -fast +yosys dfflegalize -cell \$_DFF_P_ 0 +yosys abc -lut 4 -dress +yosys clean +yosys techmap -map [file dirname [file normalize $argv0]]/example_map.v +yosys clean +yosys hierarchy -check +yosys stat + +if {$argc > 0} { yosys write_json [lindex $argv 0] } diff --git a/generic/viaduct/example/viaduct_example.sh b/generic/viaduct/example/viaduct_example.sh new file mode 100755 index 00000000..0842df20 --- /dev/null +++ b/generic/viaduct/example/viaduct_example.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -ex +# Run synthesis +yosys -p "tcl synth_viaduct_example.tcl blinky.json" ../../examples/blinky.v +# Run PnR +${NEXTPNR:-../../../build/nextpnr-generic} --uarch example --json blinky.json diff --git a/generic/viaduct_api.cc b/generic/viaduct_api.cc new file mode 100644 index 00000000..8a7b6313 --- /dev/null +++ b/generic/viaduct_api.cc @@ -0,0 +1,118 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@ds0.me> + * + * 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 "viaduct_api.h" +#include "nextpnr.h" + +// Default implementations for Viaduct API hooks + +NEXTPNR_NAMESPACE_BEGIN + +void ViaductAPI::init(Context *ctx) { this->ctx = ctx; } + +std::vector<IdString> ViaductAPI::getCellTypes() const +{ + pool<IdString> cell_types; + for (auto bel : ctx->bels) { + cell_types.insert(bel.type); + } + + return std::vector<IdString>{cell_types.begin(), cell_types.end()}; +} +BelBucketId ViaductAPI::getBelBucketForBel(BelId bel) const { return ctx->getBelType(bel); } +BelBucketId ViaductAPI::getBelBucketForCellType(IdString cell_type) const { return cell_type; } +bool ViaductAPI::isValidBelForCellType(IdString cell_type, BelId bel) const +{ + return ctx->getBelType(bel) == cell_type; +} + +delay_t ViaductAPI::estimateDelay(WireId src, WireId dst) const +{ + const WireInfo &s = ctx->wire_info(src); + const WireInfo &d = ctx->wire_info(dst); + int dx = abs(s.x - d.x); + int dy = abs(s.y - d.y); + return (dx + dy) * ctx->args.delayScale + ctx->args.delayOffset; +} +delay_t ViaductAPI::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const +{ + NPNR_UNUSED(src_pin); + NPNR_UNUSED(dst_pin); + auto driver_loc = ctx->getBelLocation(src_bel); + auto sink_loc = ctx->getBelLocation(dst_bel); + + int dx = abs(sink_loc.x - driver_loc.x); + int dy = abs(sink_loc.y - driver_loc.y); + return (dx + dy) * ctx->args.delayScale + ctx->args.delayOffset; +} +ArcBounds ViaductAPI::getRouteBoundingBox(WireId src, WireId dst) const +{ + ArcBounds bb; + int src_x = ctx->wire_info(src).x; + int src_y = ctx->wire_info(src).y; + int dst_x = ctx->wire_info(dst).x; + int dst_y = ctx->wire_info(dst).y; + + bb.x0 = src_x; + bb.y0 = src_y; + bb.x1 = src_x; + bb.y1 = src_y; + + auto extend = [&](int x, int y) { + bb.x0 = std::min(bb.x0, x); + bb.x1 = std::max(bb.x1, x); + bb.y0 = std::min(bb.y0, y); + bb.y1 = std::max(bb.y1, y); + }; + extend(dst_x, dst_y); + return bb; +} + +ViaductArch *ViaductArch::list_head; +ViaductArch::ViaductArch(const std::string &name) : name(name) +{ + list_next = ViaductArch::list_head; + ViaductArch::list_head = this; +} +std::string ViaductArch::list() +{ + std::string result; + ViaductArch *cursor = ViaductArch::list_head; + while (cursor) { + if (!result.empty()) + result += ", "; + result += cursor->name; + cursor = cursor->list_next; + } + return result; +} +std::unique_ptr<ViaductAPI> ViaductArch::create(const std::string &name, const dict<std::string, std::string> &args) +{ + ViaductArch *cursor = ViaductArch::list_head; + while (cursor) { + if (cursor->name != name) { + cursor = cursor->list_next; + continue; + } + return cursor->create(args); + } + return {}; +} + +NEXTPNR_NAMESPACE_END diff --git a/generic/viaduct_api.h b/generic/viaduct_api.h new file mode 100644 index 00000000..bc9b4311 --- /dev/null +++ b/generic/viaduct_api.h @@ -0,0 +1,114 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@ds0.me> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef VIADUCT_API_H +#define VIADUCT_API_H + +#include "nextpnr_namespaces.h" +#include "nextpnr_types.h" + +NEXTPNR_NAMESPACE_BEGIN + +/* +Viaduct -- a series of small arches + +Viaduct is a framework that provides an 'inbetween' step between nextpnr-generic +using Python bindings and a full-custom arch. + +It allows an arch to programmatically build a set of bels (placement locations) +and a routing graph in-memory at startup; and then hook into nextpnr's flow +and validity checking rules at runtime with custom C++ code. + +To create a Viaduct 'uarch', the following are required: + - an implementation of ViaductAPI. At a minimum; you will need to use ctx->addBel, ctx->addWire and ctx->addPip to +create the graph of placement and routing resources in-memory. Also implement any placement validity checking required - +like rules for how LUTs and FFs can be placed together in a SLICE. + - an instance of a struct deriving from ViaductArch - this is how the uarch is discovered. Override create(args) to +create an instance of your ViaductAPI implementation. + - these should be within C++ files in a new subfolder of 'viaduct'. Add the name of this subfolder to the list of +VIADUCT_UARCHES in family.cmake if building in-tree. + +For an example of how these pieces fit together; see 'viaduct/example' which implements a small synthetic architecture +using this framework. + +*/ + +struct Context; + +struct ViaductAPI +{ + virtual void init(Context *ctx); + Context *ctx; + + // --- Bel functions --- + // Called when a bel is placed/unplaced (with cell=nullptr for a unbind) + virtual void notifyBelChange(BelId bel, CellInfo *cell) {} + // This only needs to return false if a bel is disabled for a microarch-specific reason and not just because it's + // bound (which the base generic will deal with) + virtual bool checkBelAvail(BelId bel) const { return true; } + // Mirror the ArchAPI functions - see archapi.md + virtual std::vector<IdString> getCellTypes() const; + virtual BelBucketId getBelBucketForBel(BelId bel) const; + virtual BelBucketId getBelBucketForCellType(IdString cell_type) const; + virtual bool isValidBelForCellType(IdString cell_type, BelId bel) const; + virtual bool isBelLocationValid(BelId bel) const { return true; } + + // --- Wire and pip functions --- + // Called when a wire/pip is placed/unplaced (with net=nullptr for a unbind) + virtual void notifyWireChange(WireId wire, NetInfo *net) {} + virtual void notifyPipChange(PipId pip, NetInfo *net) {} + // These only need to return false if a wire/pip is disabled for a microarch-specific reason and not just because + // it's bound (which the base arch will deal with) + virtual bool checkWireAvail(WireId wire) const { return true; } + virtual bool checkPipAvail(PipId pip) const { return true; } + virtual bool checkPipAvailForNet(PipId pip, NetInfo *net) const { return checkPipAvail(pip); }; + + // --- Route lookahead --- + virtual delay_t estimateDelay(WireId src, WireId dst) const; + virtual delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const; + virtual ArcBounds getRouteBoundingBox(WireId src, WireId dst) const; + + // --- Flow hooks --- + virtual void pack(){}; // replaces the pack function + // Called before and after main placement and routing + virtual void prePlace(){}; + virtual void postPlace(){}; + virtual void preRoute(){}; + virtual void postRoute(){}; + + virtual ~ViaductAPI(){}; +}; + +struct ViaductArch +{ + static ViaductArch *list_head; + ViaductArch *list_next = nullptr; + + std::string name; + ViaductArch(const std::string &name); + ~ViaductArch(){}; + virtual std::unique_ptr<ViaductAPI> create(const dict<std::string, std::string> &args) = 0; + + static std::string list(); + static std::unique_ptr<ViaductAPI> create(const std::string &name, const dict<std::string, std::string> &args); +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/generic/viaduct_constids.h b/generic/viaduct_constids.h new file mode 100644 index 00000000..82180953 --- /dev/null +++ b/generic/viaduct_constids.h @@ -0,0 +1,74 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@ds0.me> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef VIADUCT_CONSTIDS_H +#define VIADUCT_CONSTIDS_H + +/* +This enables use of 'constids' similar to a 'true' nextpnr arch in a viaduct uarch. +To use: + - create a 'constids.inc' file in your uarch folder containing one ID per line; inside X( ) + - set the VIADUCT_CONSTIDS macro to the path to this file relative to the generic arch base + - in your main file; also define GEN_INIT_CONSTIDS to create init_uarch_constids(Context*) which you should call in +init + - include this file +*/ + +#include "nextpnr_namespaces.h" + +#ifdef VIADUCT_MAIN +#include "idstring.h" +#endif + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +#ifndef Q_MOC_RUN +enum ConstIds +{ + ID_NONE +#define X(t) , ID_##t +#include VIADUCT_CONSTIDS +#undef X + , +}; + +#define X(t) static constexpr auto id_##t = IdString(ID_##t); +#include VIADUCT_CONSTIDS +#undef X +#endif + +#ifdef GEN_INIT_CONSTIDS + +void init_uarch_constids(Context *ctx) +{ +#define X(t) IdString::initialize_add(ctx, #t, ID_##t); + +#include VIADUCT_CONSTIDS + +#undef X +} + +#endif + +} // namespace + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/generic/viaduct_helpers.cc b/generic/viaduct_helpers.cc new file mode 100644 index 00000000..36bdd6be --- /dev/null +++ b/generic/viaduct_helpers.cc @@ -0,0 +1,163 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@ds0.me> + * + * 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 "viaduct_helpers.h" +#include "design_utils.h" +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +void ViaductHelpers::resize_ids(int x, int y) +{ + NPNR_ASSERT(x >= 0 && y >= 0 && x <= 20000 && y <= 20000); + while (int(x_ids.size()) <= x) { + IdString next = ctx->id(stringf("X%d", int(x_ids.size()))); + x_ids.push_back(next); + } + while (int(y_ids.size()) <= y) { + IdString next = ctx->id(stringf("Y%d", int(y_ids.size()))); + y_ids.push_back(next); + } +} + +IdStringList ViaductHelpers::xy_id(int x, int y, IdString base) +{ + resize_ids(x, y); + std::array<IdString, 3> result{x_ids.at(x), y_ids.at(y), base}; + return IdStringList(result); +} + +IdStringList ViaductHelpers::xy_id(int x, int y, IdStringList base) +{ + resize_ids(x, y); + std::array<IdString, 2> prefix{x_ids.at(x), y_ids.at(y)}; + return IdStringList::concat(IdStringList(prefix), base); +} + +void ViaductHelpers::remove_nextpnr_iobs(const pool<CellTypePort> &top_ports) +{ + std::vector<IdString> to_remove; + for (auto &cell : ctx->cells) { + auto &ci = *cell.second; + if (!ci.type.in(ctx->id("$nextpnr_ibuf"), ctx->id("$nextpnr_obuf"), ctx->id("$nextpnr_iobuf"))) + continue; + NetInfo *i = get_net_or_empty(&ci, ctx->id("I")); + if (i && i->driver.cell) { + if (!top_ports.count(CellTypePort(i->driver))) + log_error("Top-level port '%s' driven by illegal port %s.%s\n", ctx->nameOf(&ci), + ctx->nameOf(i->driver.cell), ctx->nameOf(i->driver.port)); + } + NetInfo *o = get_net_or_empty(&ci, ctx->id("O")); + if (o) { + for (auto &usr : o->users) { + if (!top_ports.count(CellTypePort(usr))) + log_error("Top-level port '%s' driving illegal port %s.%s\n", ctx->nameOf(&ci), + ctx->nameOf(usr.cell), ctx->nameOf(usr.port)); + } + } + disconnect_port(ctx, &ci, ctx->id("I")); + disconnect_port(ctx, &ci, ctx->id("O")); + to_remove.push_back(ci.name); + } + for (IdString cell_name : to_remove) + ctx->cells.erase(cell_name); +} + +int ViaductHelpers::constrain_cell_pairs(const pool<CellTypePort> &src_ports, const pool<CellTypePort> &sink_ports, + int delta_z) +{ + int constrained = 0; + for (auto &cell : ctx->cells) { + auto &ci = *cell.second; + if (ci.cluster != ClusterId()) + continue; // don't constrain already-constrained cells + bool done = false; + for (auto &port : ci.ports) { + // look for starting source ports + if (port.second.type != PORT_OUT || !port.second.net) + continue; + if (!src_ports.count(CellTypePort(ci.type, port.first))) + continue; + for (auto &usr : port.second.net->users) { + if (!sink_ports.count(CellTypePort(usr))) + continue; + if (usr.cell->cluster != ClusterId()) + continue; + // Add the constraint + ci.cluster = ci.name; + ci.constr_abs_z = false; + ci.constr_children.push_back(usr.cell); + usr.cell->cluster = ci.name; + usr.cell->constr_x = 0; + usr.cell->constr_y = 0; + usr.cell->constr_z = delta_z; + usr.cell->constr_abs_z = false; + ++constrained; + done = true; + break; + } + if (done) + break; + } + } + return constrained; +} + +void ViaductHelpers::replace_constants(CellTypePort vcc_driver, CellTypePort gnd_driver, + const dict<IdString, Property> &vcc_params, + const dict<IdString, Property> &gnd_params) +{ + CellInfo *vcc_drv = ctx->createCell(ctx->id("$PACKER_VCC_DRV"), vcc_driver.cell_type); + vcc_drv->addOutput(vcc_driver.port); + for (auto &p : vcc_params) + vcc_drv->params[p.first] = p.second; + + CellInfo *gnd_drv = ctx->createCell(ctx->id("$PACKER_GND_DRV"), gnd_driver.cell_type); + gnd_drv->addOutput(gnd_driver.port); + for (auto &p : gnd_params) + gnd_drv->params[p.first] = p.second; + + NetInfo *vcc_net = ctx->createNet(ctx->id("$PACKER_VCC")); + NetInfo *gnd_net = ctx->createNet(ctx->id("$PACKER_GND")); + + std::vector<IdString> trim_cells; + std::vector<IdString> trim_nets; + for (auto &net : ctx->nets) { + auto &ni = *net.second; + if (!ni.driver.cell) + continue; + if (ni.driver.cell->type != ctx->id("GND") && ni.driver.cell->type != ctx->id("VCC")) + continue; + NetInfo *replace = (ni.driver.cell->type == ctx->id("VCC")) ? vcc_net : gnd_net; + for (auto &usr : ni.users) { + usr.cell->ports.at(usr.port).net = replace; + replace->users.push_back(usr); + } + trim_cells.push_back(ni.driver.cell->name); + trim_nets.push_back(ni.name); + } + for (IdString cell_name : trim_cells) + ctx->cells.erase(cell_name); + for (IdString net_name : trim_nets) + ctx->nets.erase(net_name); +} + +NEXTPNR_NAMESPACE_END diff --git a/generic/viaduct_helpers.h b/generic/viaduct_helpers.h new file mode 100644 index 00000000..8cba8411 --- /dev/null +++ b/generic/viaduct_helpers.h @@ -0,0 +1,82 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@ds0.me> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef VIADUCT_HELPERS_H +#define VIADUCT_HELPERS_H + +#include "nextpnr_namespaces.h" +#include "nextpnr_types.h" + +NEXTPNR_NAMESPACE_BEGIN + +/* +Viaduct -- a series of small arches + +See viaduct_api.h for more background. + +viaduct_helpers provides some features for building up arches using the viaduct API +*/ + +// Used to configure various generic pack functions +struct CellTypePort +{ + CellTypePort() : cell_type(), port(){}; + CellTypePort(IdString cell_type, IdString port) : cell_type(cell_type), port(port){}; + explicit CellTypePort(const PortRef &net_port) + : cell_type(net_port.cell ? net_port.cell->type : IdString()), port(net_port.port){}; + inline bool operator==(const CellTypePort &other) const + { + return cell_type == other.cell_type && port == other.port; + } + inline bool operator!=(const CellTypePort &other) const + { + return cell_type != other.cell_type || port != other.port; + } + inline unsigned hash() const { return mkhash(cell_type.hash(), port.hash()); } + IdString cell_type, port; +}; + +struct ViaductHelpers +{ + ViaductHelpers(){}; + Context *ctx; + void init(Context *ctx) { this->ctx = ctx; } + // IdStringList components for x and y locations + std::vector<IdString> x_ids, y_ids; + void resize_ids(int x, int y); + // Get an IdStringList for a hierarchical ID + // Because this uses an IdStringList with seperate X and Y components; this will be much more efficient than + // creating unique strings for each object in each X and Y position + IdStringList xy_id(int x, int y, IdString base); + IdStringList xy_id(int x, int y, IdStringList base); + // Common packing functions + // Remove nextpnr-inserted IO buffers; where IO buffer insertion is done in synthesis + // expects a set of top-level port types + void remove_nextpnr_iobs(const pool<CellTypePort> &top_ports); + // Constrain cells with certain port connection patterns together with a fixed z-offset + int constrain_cell_pairs(const pool<CellTypePort> &src_ports, const pool<CellTypePort> &sink_ports, int delta_z); + // Replace constants with given driving cells + void replace_constants(CellTypePort vcc_driver, CellTypePort gnd_driver, + const dict<IdString, Property> &vcc_params = {}, + const dict<IdString, Property> &gnd_params = {}); +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/gowin/CMakeLists.txt b/gowin/CMakeLists.txt index a356a84b..5ec32128 100644 --- a/gowin/CMakeLists.txt +++ b/gowin/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.5) project(chipdb-gowin NONE) -set(ALL_GOWIN_DEVICES GW1N-1 GW1N-4 GW1N-9 GW1NS-2) +set(ALL_GOWIN_DEVICES GW1N-1 GW1N-4 GW1N-9 GW1NS-2 GW1NS-4) set(GOWIN_DEVICES ${ALL_GOWIN_DEVICES} CACHE STRING "Include support for these Gowin devices (available: ${ALL_GOWIN_DEVICES})") message(STATUS "Enabled Gowin devices: ${GOWIN_DEVICES}") diff --git a/gowin/arch.cc b/gowin/arch.cc index 99e0ce61..8c654b2e 100644 --- a/gowin/arch.cc +++ b/gowin/arch.cc @@ -612,7 +612,7 @@ void Arch::addMuxBels(const DatabasePOD *db, int row, int col) int gcol = col + 1; // no MUX2_LUT8 in the last column - if (j == 7 && col == getGridDimX() - 1) { + if (j == 7 && col == getGridDimX() - 2) { continue; } @@ -870,6 +870,39 @@ Arch::Arch(ArchArgs args) : args(args) snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_OEN, id(buf)); break; + // Simplified IO + case ID_IOBJS: + z++; /* fall-through*/ + case ID_IOBIS: + z++; /* fall-through*/ + case ID_IOBHS: + z++; /* fall-through*/ + case ID_IOBGS: + z++; /* fall-through*/ + case ID_IOBFS: + z++; /* fall-through*/ + case ID_IOBES: + z++; /* fall-through*/ + case ID_IOBDS: + z++; /* fall-through*/ + case ID_IOBCS: + z++; /* fall-through*/ + case ID_IOBBS: + z++; /* fall-through*/ + case ID_IOBAS: + snprintf(buf, 32, "R%dC%d_IOB%c", row + 1, col + 1, 'A' + z); + belname = id(buf); + addBel(belname, id_IOBS, Loc(col, row, z), false); + portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_O)->src_id); + snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); + addBelOutput(belname, id_O, id(buf)); + portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_I)->src_id); + snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); + addBelInput(belname, id_I, id(buf)); + portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OE)->src_id); + snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); + addBelInput(belname, id_OEN, id(buf)); + break; default: break; @@ -1171,11 +1204,12 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const return (dx + dy) * args.delayScale + args.delayOffset; } -delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const { - const auto &driver = net_info->driver; - auto driver_loc = getBelLocation(driver.cell->bel); - auto sink_loc = getBelLocation(sink.cell->bel); + NPNR_UNUSED(src_pin); + NPNR_UNUSED(dst_pin); + auto driver_loc = getBelLocation(src_bel); + auto sink_loc = getBelLocation(dst_bel); int dx = abs(sink_loc.x - driver_loc.x); int dy = abs(sink_loc.y - driver_loc.y); diff --git a/gowin/arch.h b/gowin/arch.h index aa751a4f..9f969f54 100644 --- a/gowin/arch.h +++ b/gowin/arch.h @@ -298,7 +298,7 @@ struct Arch : BaseArch<ArchRanges> dict<DecalId, std::vector<GraphicElement>> decal_graphics; - int gridDimX, gridDimY; + int gridDimX = 0, gridDimY = 0; std::vector<std::vector<int>> tileBelDimZ; std::vector<std::vector<int>> tilePipDimZ; @@ -419,7 +419,7 @@ struct Arch : BaseArch<ArchRanges> const std::vector<GroupId> &getGroupGroups(GroupId group) const override; delay_t estimateDelay(WireId src, WireId dst) const override; - delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override; + delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const override; delay_t getDelayEpsilon() const override { return 0.01; } delay_t getRipupDelayPenalty() const override { return 0.4; } float getDelayNS(delay_t v) const override { return v; } diff --git a/gowin/cells.cc b/gowin/cells.cc index 57f3ab9c..dce3f456 100644 --- a/gowin/cells.cc +++ b/gowin/cells.cc @@ -64,14 +64,14 @@ std::unique_ptr<CellInfo> create_generic_cell(Context *ctx, IdString type, std:: add_port(ctx, new_cell.get(), id_I1, PORT_IN); add_port(ctx, new_cell.get(), id_SEL, PORT_IN); add_port(ctx, new_cell.get(), id_OF, PORT_OUT); - } else if (type == id_IOB) { + } else if (type == id_IOB || type == id_IOBS) { new_cell->params[id_INPUT_USED] = 0; new_cell->params[id_OUTPUT_USED] = 0; new_cell->params[id_ENABLE_USED] = 0; add_port(ctx, new_cell.get(), id_PAD, PORT_INOUT); add_port(ctx, new_cell.get(), id_I, PORT_IN); - add_port(ctx, new_cell.get(), id_EN, PORT_IN); + add_port(ctx, new_cell.get(), id_OEN, PORT_IN); add_port(ctx, new_cell.get(), id_O, PORT_OUT); } else { log_error("unable to create generic cell of type %s\n", type.c_str(ctx)); @@ -140,9 +140,17 @@ void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_l void gwio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *iob, pool<IdString> &todelete_cells) { if (nxio->type == id_IBUF) { + if (iob->type == id_IOBS) { + // VCC -> OEN + connect_port(ctx, ctx->nets[ctx->id("$PACKER_VCC_NET")].get(), iob, id_OEN); + } iob->params[id_INPUT_USED] = 1; replace_port(nxio, id_O, iob, id_O); } else if (nxio->type == id_OBUF) { + if (iob->type == id_IOBS) { + // VSS -> OEN + connect_port(ctx, ctx->nets[ctx->id("$PACKER_GND_NET")].get(), iob, id_OEN); + } iob->params[id_OUTPUT_USED] = 1; replace_port(nxio, id_I, iob, id_I); } else if (nxio->type == id_TBUF) { diff --git a/gowin/constids.inc b/gowin/constids.inc index 7de754fc..6a730d5d 100644 --- a/gowin/constids.inc +++ b/gowin/constids.inc @@ -358,6 +358,19 @@ X(IOBH) X(IOBI) X(IOBJ) +// simplified iobs +X(IOBS) +X(IOBAS) +X(IOBBS) +X(IOBCS) +X(IOBDS) +X(IOBES) +X(IOBFS) +X(IOBGS) +X(IOBHS) +X(IOBIS) +X(IOBJS) + // Wide LUTs X(MUX2_LUT5) X(MUX2_LUT6) diff --git a/gowin/main.cc b/gowin/main.cc index c6697ecc..66de26aa 100644 --- a/gowin/main.cc +++ b/gowin/main.cc @@ -54,7 +54,7 @@ po::options_description GowinCommandHandler::getArchOptions() std::unique_ptr<Context> GowinCommandHandler::createContext(dict<std::string, Property> &values) { - std::regex devicere = std::regex("GW1N([A-Z]*)-(LV|UV|UX)([0-9])(C?).*"); + std::regex devicere = std::regex("GW1N(S?)[A-Z]*-(LV|UV|UX)([0-9])(C?).*"); std::smatch match; std::string device = vm["device"].as<std::string>(); if (!std::regex_match(device, match, devicere)) { @@ -65,11 +65,7 @@ std::unique_ptr<Context> GowinCommandHandler::createContext(dict<std::string, Pr // GW1N and GW1NR variants share the same database. // Most Gowin devices are a System in Package with some SDRAM wirebonded to a GPIO bank. // However, it appears that the S series with embedded ARM core are unique silicon. - if (match[1].str() == "S") { - snprintf(buf, 36, "GW1NS-%s", match[3].str().c_str()); - } else { - snprintf(buf, 36, "GW1N-%s", match[3].str().c_str()); - } + snprintf(buf, 36, "GW1N%s-%s", match[1].str().c_str(), match[3].str().c_str()); chipArgs.family = buf; chipArgs.partnumber = match[0]; return std::unique_ptr<Context>(new Context(chipArgs)); diff --git a/gowin/pack.cc b/gowin/pack.cc index cb63f1c9..553eeb4e 100644 --- a/gowin/pack.cc +++ b/gowin/pack.cc @@ -629,7 +629,7 @@ static void pack_constants(Context *ctx) std::vector<IdString> dead_nets; - bool gnd_used = false; + bool gnd_used = true; // XXX May be needed for simplified IO for (auto &net : ctx->nets) { NetInfo *ni = net.second.get(); @@ -718,8 +718,27 @@ static void pack_io(Context *ctx) } packed_cells.insert(iob->name); } + // what type to create + IdString new_cell_type = id_IOB; + std::string constr_bel_name = std::string(""); + // check whether the given IO is limited to simplified IO cells + auto constr_bel = ci->attrs.find(id_BEL); + if (constr_bel != ci->attrs.end()) { + constr_bel_name = constr_bel->second.as_string(); + } + constr_bel = iob->attrs.find(id_BEL); + if (constr_bel != iob->attrs.end()) { + constr_bel_name = constr_bel->second.as_string(); + } + if (!constr_bel_name.empty()) { + BelId constr_bel = ctx->getBelByNameStr(constr_bel_name); + if (constr_bel != BelId()) { + new_cell_type = ctx->bels[constr_bel].type; + } + } + // Create a IOB buffer - std::unique_ptr<CellInfo> ice_cell = create_generic_cell(ctx, id_IOB, ci->name.str(ctx) + "$iob"); + std::unique_ptr<CellInfo> ice_cell = create_generic_cell(ctx, new_cell_type, ci->name.str(ctx) + "$iob"); gwio_to_iob(ctx, ci, ice_cell.get(), packed_cells); new_cells.push_back(std::move(ice_cell)); auto gwiob = new_cells.back().get(); diff --git a/ice40/arch.h b/ice40/arch.h index 5162285c..3563baad 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -784,7 +784,7 @@ struct Arch : BaseArch<ArchRanges> // ------------------------------------------------- delay_t estimateDelay(WireId src, WireId dst) const override; - delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override; + delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const override; delay_t getDelayEpsilon() const override { return 20; } delay_t getRipupDelayPenalty() const override { return 200; } float getDelayNS(delay_t v) const override { return v * 0.001; } diff --git a/ice40/delay.cc b/ice40/delay.cc index 740057f1..a00cc259 100644 --- a/ice40/delay.cc +++ b/ice40/delay.cc @@ -188,13 +188,13 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const return v; } -delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const { - const auto &driver = net_info->driver; - auto driver_loc = getBelLocation(driver.cell->bel); - auto sink_loc = getBelLocation(sink.cell->bel); + NPNR_UNUSED(dst_pin); + auto driver_loc = getBelLocation(src_bel); + auto sink_loc = getBelLocation(dst_bel); - if (driver.port == id_COUT) { + if (src_pin == id_COUT) { if (driver_loc.y == sink_loc.y) return 0; return 250; diff --git a/machxo2/arch.cc b/machxo2/arch.cc index a201adf1..6c0e48ce 100644 --- a/machxo2/arch.cc +++ b/machxo2/arch.cc @@ -387,16 +387,17 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const return (abs(dst.location.x - src.location.x) + abs(dst.location.y - src.location.y)) * (0.01 + 0.01); } -delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const { - BelId src = net_info->driver.cell->bel; - BelId dst = sink.cell->bel; + NPNR_UNUSED(src_pin); + NPNR_UNUSED(dst_pin); - NPNR_ASSERT(src != BelId()); - NPNR_ASSERT(dst != BelId()); + NPNR_ASSERT(src_bel != BelId()); + NPNR_ASSERT(dst_bel != BelId()); // TODO: Same deal applies here as with estimateDelay. - return (abs(dst.location.x - src.location.x) + abs(dst.location.y - src.location.y)) * (0.01 + 0.01); + return (abs(dst_bel.location.x - src_bel.location.x) + abs(dst_bel.location.y - src_bel.location.y)) * + (0.01 + 0.01); } ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const diff --git a/machxo2/arch.h b/machxo2/arch.h index f5ccb4f6..29d8c3ff 100644 --- a/machxo2/arch.h +++ b/machxo2/arch.h @@ -626,7 +626,7 @@ struct Arch : BaseArch<ArchRanges> // Delay delay_t estimateDelay(WireId src, WireId dst) const override; - delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override; + delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const override; delay_t getDelayEpsilon() const override { return 0.001; } delay_t getRipupDelayPenalty() const override { return 0.015; } float getDelayNS(delay_t v) const override { return v; } diff --git a/machxo2/bitstream.cc b/machxo2/bitstream.cc index ed67975a..f624d91b 100644 --- a/machxo2/bitstream.cc +++ b/machxo2/bitstream.cc @@ -19,6 +19,7 @@ */ #include <fstream> +#include <iostream> #include "bitstream.h" #include "config.h" @@ -59,16 +60,25 @@ static std::string get_trellis_wirename(Context *ctx, Location loc, WireId wire) // Handle MachXO2's wonderful naming quirks for wires in left/right tiles, whose // relative coords push them outside the bounds of the chip. + // Indents are based on wires proximity/purpose. auto is_pio_wire = [](std::string name) { + // clang-format off return (name.find("DI") != std::string::npos || name.find("JDI") != std::string::npos || - name.find("PADD") != std::string::npos || name.find("INDD") != std::string::npos || - name.find("IOLDO") != std::string::npos || name.find("IOLTO") != std::string::npos || - name.find("JCE") != std::string::npos || name.find("JCLK") != std::string::npos || - name.find("JLSR") != std::string::npos || name.find("JONEG") != std::string::npos || - name.find("JOPOS") != std::string::npos || name.find("JTS") != std::string::npos || + name.find("PADD") != std::string::npos || name.find("INDD") != std::string::npos || + name.find("IOLDO") != std::string::npos || name.find("IOLTO") != std::string::npos || + // JCE0-3, JCLK0-3, JLSR0-3 connect to PIO wires named JCEA-D, JCLKA-D, JLSRA-D. + name.find("JCEA") != std::string::npos || name.find("JCEB") != std::string::npos || + name.find("JCEC") != std::string::npos || name.find("JCED") != std::string::npos || + name.find("JCLKA") != std::string::npos || name.find("JCLKB") != std::string::npos || + name.find("JCLKC") != std::string::npos || name.find("JCLKD") != std::string::npos || + name.find("JLSRA") != std::string::npos || name.find("JLSRB") != std::string::npos || + name.find("JLSRC") != std::string::npos || name.find("JLSRD") != std::string::npos || + name.find("JONEG") != std::string::npos || name.find("JOPOS") != std::string::npos || + name.find("JTS") != std::string::npos || name.find("JIN") != std::string::npos || name.find("JIP") != std::string::npos || // Connections to global mux name.find("JINCK") != std::string::npos); + // clang-format on }; if (prefix2 == "G_" || prefix2 == "L_" || prefix2 == "R_" || prefix7 == "BRANCH_") @@ -92,10 +102,19 @@ static std::string get_trellis_wirename(Context *ctx, Location loc, WireId wire) if (loc == wire.location) { // TODO: JINCK is not currently handled by this. if (is_pio_wire(basename)) { - if (wire.location.x == 0) - return "W1_" + basename; - else if (wire.location.x == max_col) - return "E1_" + basename; + if (wire.location.x == 0) { + std::string pio_name = "W1_" + basename; + if (ctx->verbose) + log_info("PIO wire %s was adjusted by W1 to form Trellis name %s.\n", ctx->nameOfWire(wire), + pio_name.c_str()); + return pio_name; + } else if (wire.location.x == max_col) { + std::string pio_name = "E1_" + basename; + if (ctx->verbose) + log_info("PIO wire %s was adjusted by E1 to form Trellis name %s.\n", ctx->nameOfWire(wire), + pio_name.c_str()); + return pio_name; + } } return basename; } diff --git a/machxo2/examples/demo-vhdl.sh b/machxo2/examples/demo-vhdl.sh index 4bdab54a..ed1f7d80 100644 --- a/machxo2/examples/demo-vhdl.sh +++ b/machxo2/examples/demo-vhdl.sh @@ -19,6 +19,6 @@ set -ex ${YOSYS:-yosys} -p "ghdl --std=08 prims.vhd ${1}.vhd -e; attrmap -tocase LOC synth_machxo2 -json ${1}-vhdl.json" -${NEXTPNR:-../../nextpnr-machxo2} --1200 --package QFN32 --no-iobs --json $1-vhdl.json --textcfg $1-vhdl.txt +${NEXTPNR:-../../nextpnr-machxo2} --1200 --package QFN32 --json $1-vhdl.json --textcfg $1-vhdl.txt ecppack --compress $DB_ARG $1-vhdl.txt $1-vhdl.bit tinyproga -b $1-vhdl.bit diff --git a/machxo2/examples/demo.sh b/machxo2/examples/demo.sh index 00cb0cd0..634fbb4d 100644 --- a/machxo2/examples/demo.sh +++ b/machxo2/examples/demo.sh @@ -17,6 +17,6 @@ fi set -ex ${YOSYS:-yosys} -p "synth_machxo2 -json $1.json" $1.v -${NEXTPNR:-../../nextpnr-machxo2} --1200 --package QFN32 --no-iobs --json $1.json --textcfg $1.txt +${NEXTPNR:-../../nextpnr-machxo2} --1200 --package QFN32 --json $1.json --textcfg $1.txt ecppack --compress $DB_ARG $1.txt $1.bit tinyproga -b $1.bit diff --git a/machxo2/examples/mitertest.sh b/machxo2/examples/mitertest.sh index cfae28b7..b7ec2695 100644 --- a/machxo2/examples/mitertest.sh +++ b/machxo2/examples/mitertest.sh @@ -61,6 +61,7 @@ do_smt() { miter -equiv -make_assert gold gate ${2}${1}_miter hierarchy -top ${2}${1}_miter; proc; opt_clean + flatten t:*FACADE_IO* write_verilog ${2}${1}_miter.v write_smt2 ${2}${1}_miter.smt2" @@ -71,8 +72,8 @@ do_smt() { set -ex ${YOSYS:-yosys} -p "read_verilog ${1}.v - synth_machxo2 -noiopad -json ${1}.json" -${NEXTPNR:-../../nextpnr-machxo2} $NEXTPNR_MODE --1200 --package QFN32 --no-iobs --json ${1}.json --write ${2}${1}.json + synth_machxo2 -json ${1}.json" +${NEXTPNR:-../../nextpnr-machxo2} $NEXTPNR_MODE --1200 --package QFN32 --json ${1}.json --write ${2}${1}.json ${YOSYS:-yosys} -p "read_verilog -lib +/machxo2/cells_sim.v read_json ${2}${1}.json clean -purge diff --git a/machxo2/examples/simple.sh b/machxo2/examples/simple.sh index 1da60933..69706b9c 100644 --- a/machxo2/examples/simple.sh +++ b/machxo2/examples/simple.sh @@ -26,7 +26,7 @@ set -ex ${YOSYS:-yosys} -p "read_verilog ${1}.v synth_machxo2 -json ${1}.json show -format png -prefix ${1}" -${NEXTPNR:-../../nextpnr-machxo2} $NEXTPNR_MODE --1200 --package QFN32 --no-iobs --json ${1}.json --write ${2}${1}.json +${NEXTPNR:-../../nextpnr-machxo2} $NEXTPNR_MODE --1200 --package QFN32 --json ${1}.json --write ${2}${1}.json ${YOSYS:-yosys} -p "read_verilog -lib +/machxo2/cells_sim.v read_json ${2}${1}.json clean -purge diff --git a/machxo2/examples/simtest.sh b/machxo2/examples/simtest.sh index 2c7f6f30..0adf1751 100644 --- a/machxo2/examples/simtest.sh +++ b/machxo2/examples/simtest.sh @@ -30,7 +30,7 @@ set -ex ${YOSYS:-yosys} -p "read_verilog ${1}.v synth_machxo2 -json ${1}.json" -${NEXTPNR:-../../nextpnr-machxo2} $NEXTPNR_MODE --1200 --package QFN32 --no-iobs --json ${1}.json --write ${2}${1}.json +${NEXTPNR:-../../nextpnr-machxo2} $NEXTPNR_MODE --1200 --package QFN32 --json ${1}.json --write ${2}${1}.json ${YOSYS:-yosys} -p "read_verilog -lib +/machxo2/cells_sim.v read_json ${2}${1}.json clean -purge diff --git a/machxo2/main.cc b/machxo2/main.cc index 53b765fb..e29e117b 100644 --- a/machxo2/main.cc +++ b/machxo2/main.cc @@ -69,7 +69,6 @@ po::options_description MachXO2CommandHandler::getArchOptions() // specific.add_options()("lpf", po::value<std::vector<std::string>>(), "LPF pin constraint file(s)"); - specific.add_options()("no-iobs", "disable automatic IO buffer insertion (unimplemented- always enabled)"); return specific; } @@ -108,8 +107,6 @@ std::unique_ptr<Context> MachXO2CommandHandler::createContext(dict<std::string, } auto ctx = std::unique_ptr<Context>(new Context(chipArgs)); - if (vm.count("no-iobs")) - ctx->settings[ctx->id("disable_iobs")] = Property::State::S1; return ctx; } diff --git a/machxo2/pack.cc b/machxo2/pack.cc index 66d2d411..c53229ba 100644 --- a/machxo2/pack.cc +++ b/machxo2/pack.cc @@ -229,6 +229,45 @@ static bool is_nextpnr_iob(Context *ctx, CellInfo *cell) static bool is_facade_iob(const Context *ctx, const CellInfo *cell) { return cell->type == id_FACADE_IO; } +static bool nextpnr_iob_connects_only_facade_iob(Context *ctx, CellInfo *iob, NetInfo *&top) +{ + NPNR_ASSERT(is_nextpnr_iob(ctx, iob)); + + if (iob->type == ctx->id("$nextpnr_ibuf")) { + NetInfo *o = iob->ports.at(id_O).net; + top = o; + + CellInfo *fio = net_only_drives(ctx, o, is_facade_iob, id_PAD, true); + return fio != nullptr; + } else if (iob->type == ctx->id("$nextpnr_obuf")) { + NetInfo *i = iob->ports.at(id_I).net; + top = i; + + // If connected to a FACADE_IO PAD, the net attached to an I port of an + // $nextpnr_obuf will not have a driver, only users; an inout port + // like PAD cannot be a driver in nextpnr. So net_driven_by won't + // return anything. We exclude the IOB as one of the two users because + // we already know that the net drives the $nextpnr_obuf. + CellInfo *fio = net_only_drives(ctx, i, is_facade_iob, id_PAD, true, iob); + return fio != nullptr; + } else if (iob->type == ctx->id("$nextpnr_iobuf")) { + NetInfo *o = iob->ports.at(id_O).net; + top = o; + + // When split_io is enabled in a frontend (it is for JSON), the I and O + // ports of a $nextpnr_iobuf are split; the I port connects to the + // driver of the original net before IOB insertion, and the O port + // connects everything else. Because FACADE_IO PADs cannot be a driver + // in nextpnr, the we can safely ignore the I port of an $nextpnr_iobuf + // for any JSON input we're interested in accepting. + CellInfo *fio_o = net_only_drives(ctx, o, is_facade_iob, id_PAD, true); + return fio_o != nullptr; + } + + // Unreachable! + NPNR_ASSERT(false); +} + // Pack IO buffers- Right now, all this does is remove $nextpnr_[io]buf cells. // User is expected to manually instantiate FACADE_IO with BEL/IO_TYPE // attributes. @@ -241,6 +280,14 @@ static void pack_io(Context *ctx) for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (is_nextpnr_iob(ctx, ci)) { + NetInfo *top; + + if (!nextpnr_iob_connects_only_facade_iob(ctx, ci, top)) + log_error("Top level net '%s' is not connected to a FACADE_IO PAD port.\n", top->name.c_str(ctx)); + + if (ctx->verbose) + log_info("Removing top-level IOBUF '%s' of type '%s'\n", ci->name.c_str(ctx), ci->type.c_str(ctx)); + for (auto &p : ci->ports) disconnect_port(ctx, ci, p.first); packed_cells.insert(ci->name); diff --git a/mistral/arch.h b/mistral/arch.h index 34e55846..471b5251 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -418,7 +418,7 @@ struct Arch : BaseArch<ArchRanges> // ------------------------------------------------- delay_t estimateDelay(WireId src, WireId dst) const override; - delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override; + delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const override; delay_t getDelayEpsilon() const override { return 10; }; delay_t getRipupDelayPenalty() const override { return 100; }; float getDelayNS(delay_t v) const override { return float(v) / 1000.0f; }; diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index e8c4dba7..eed508b3 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -123,7 +123,7 @@ struct MistralBitgen break; }; case CycloneV::FPLL: { - if (pt == CycloneV::EXTSWITCH || (pt == CycloneV::CLKEN && pi < 2)) + if (pt == CycloneV::EXTSWITCH0 || (pt == CycloneV::CLKEN && pi < 2)) cv->inv_set(pn2r.second, true); break; }; diff --git a/mistral/delay.cc b/mistral/delay.cc index bfaeb065..98ef1be6 100644 --- a/mistral/delay.cc +++ b/mistral/delay.cc @@ -239,14 +239,12 @@ DelayQuad Arch::getPipDelay(PipId pip) const return DelayQuad{308}; } -delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const { - if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId()) - return 100; - if (sink.cell->bel == BelId()) - return 100; - Loc src_loc = getBelLocation(net_info->driver.cell->bel); - Loc dst_loc = getBelLocation(sink.cell->bel); + NPNR_UNUSED(src_pin); + NPNR_UNUSED(dst_pin); + Loc src_loc = getBelLocation(src_bel); + Loc dst_loc = getBelLocation(dst_bel); return std::abs(dst_loc.y - src_loc.y) * 100 + std::abs(dst_loc.x - src_loc.x) * 100 + 100; } diff --git a/nexus/arch.cc b/nexus/arch.cc index ee7f6304..a7751425 100644 --- a/nexus/arch.cc +++ b/nexus/arch.cc @@ -119,6 +119,9 @@ Arch::Arch(ArchArgs args) : args(args) ts.bels_by_z[bel.z].tile = i; ts.bels_by_z[bel.z].index = j; } + auto &ts = tileStatus.at(i); + ts.boundwires.resize(loc.wires.size()); + ts.boundpips.resize(loc.pips.size()); } for (int i = 0; i < chip_info->width; i++) { @@ -600,16 +603,14 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const int dist_y = std::abs(src_y - dst_y); return 75 * dist_x + 75 * dist_y + 250; } -delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const { - if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId() || sink.cell->bel == BelId()) + NPNR_UNUSED(src_pin); + if (dst_pin == id_FCI) return 0; - if (sink.port == id_FCI) - return 0; - int src_x = net_info->driver.cell->bel.tile % chip_info->width, - src_y = net_info->driver.cell->bel.tile / chip_info->width; + int src_x = src_bel.tile % chip_info->width, src_y = src_bel.tile / chip_info->width; - int dst_x = sink.cell->bel.tile % chip_info->width, dst_y = sink.cell->bel.tile / chip_info->width; + int dst_x = dst_bel.tile % chip_info->width, dst_y = dst_bel.tile / chip_info->width; int dist_x = std::abs(src_x - dst_x); int dist_y = std::abs(src_y - dst_y); return 100 * dist_x + 100 * dist_y + 250; diff --git a/nexus/arch.h b/nexus/arch.h index 3e718e78..0bd1b62c 100644 --- a/nexus/arch.h +++ b/nexus/arch.h @@ -911,6 +911,7 @@ struct Arch : BaseArch<ArchRanges> { std::vector<CellInfo *> boundcells; std::vector<BelId> bels_by_z; + std::vector<NetInfo *> boundwires, boundpips; LogicTileStatus *lts = nullptr; ~TileStatus() { delete lts; } }; @@ -1014,7 +1015,7 @@ struct Arch : BaseArch<ArchRanges> return false; if (is_pseudo_pip_disabled(pip)) return false; - return BaseArch::checkPipAvail(pip); + return getBoundPipNet(pip) == nullptr; } bool checkPipAvailForNet(PipId pip, NetInfo *net) const override @@ -1023,7 +1024,8 @@ struct Arch : BaseArch<ArchRanges> return false; if (is_pseudo_pip_disabled(pip)) return false; - return BaseArch::checkPipAvailForNet(pip, net); + NetInfo *bound = getBoundPipNet(pip); + return (bound == nullptr) || (bound == net); } CellInfo *getBoundBelCell(BelId bel) const override @@ -1136,6 +1138,38 @@ struct Arch : BaseArch<ArchRanges> return range; } + void bindWire(WireId wire, NetInfo *net, PlaceStrength strength) override + { + NPNR_ASSERT(wire != WireId()); + auto &w2n_entry = tileStatus.at(wire.tile).boundwires.at(wire.index); + NPNR_ASSERT(w2n_entry == nullptr); + net->wires[wire].pip = PipId(); + net->wires[wire].strength = strength; + w2n_entry = net; + this->refreshUiWire(wire); + } + void unbindWire(WireId wire) override + { + NPNR_ASSERT(wire != WireId()); + auto &w2n_entry = tileStatus.at(wire.tile).boundwires.at(wire.index); + NPNR_ASSERT(w2n_entry != nullptr); + + auto &net_wires = w2n_entry->wires; + auto it = net_wires.find(wire); + NPNR_ASSERT(it != net_wires.end()); + + auto pip = it->second.pip; + if (pip != PipId()) { + tileStatus.at(pip.tile).boundpips.at(pip.index) = nullptr; + } + + net_wires.erase(it); + w2n_entry = nullptr; + this->refreshUiWire(wire); + } + virtual bool checkWireAvail(WireId wire) const override { return getBoundWireNet(wire) == nullptr; } + NetInfo *getBoundWireNet(WireId wire) const override { return tileStatus.at(wire.tile).boundwires.at(wire.index); } + // ------------------------------------------------- PipId getPipByName(IdStringList name) const override; @@ -1220,10 +1254,44 @@ struct Arch : BaseArch<ArchRanges> return range; } + void bindPip(PipId pip, NetInfo *net, PlaceStrength strength) override + { + NPNR_ASSERT(pip != PipId()); + + auto &p2n_entry = tileStatus.at(pip.tile).boundpips.at(pip.index); + NPNR_ASSERT(p2n_entry == nullptr); + p2n_entry = net; + + WireId dst = this->getPipDstWire(pip); + auto &w2n_entry = tileStatus.at(dst.tile).boundwires.at(dst.index); + NPNR_ASSERT(w2n_entry == nullptr); + w2n_entry = net; + net->wires[dst].pip = pip; + net->wires[dst].strength = strength; + } + + void unbindPip(PipId pip) override + { + NPNR_ASSERT(pip != PipId()); + + auto &p2n_entry = tileStatus.at(pip.tile).boundpips.at(pip.index); + NPNR_ASSERT(p2n_entry != nullptr); + WireId dst = this->getPipDstWire(pip); + + auto &w2n_entry = tileStatus.at(dst.tile).boundwires.at(dst.index); + NPNR_ASSERT(w2n_entry != nullptr); + w2n_entry = nullptr; + + p2n_entry->wires.erase(dst); + p2n_entry = nullptr; + } + + NetInfo *getBoundPipNet(PipId pip) const override { return tileStatus.at(pip.tile).boundpips.at(pip.index); } + // ------------------------------------------------- delay_t estimateDelay(WireId src, WireId dst) const override; - delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override; + delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const override; delay_t getDelayEpsilon() const override { return 20; } delay_t getRipupDelayPenalty() const override; delay_t getWireRipupDelayPenalty(WireId wire) const; diff --git a/nexus/constids.inc b/nexus/constids.inc index 48b0ca2b..ca6bed5a 100644 --- a/nexus/constids.inc +++ b/nexus/constids.inc @@ -109,6 +109,7 @@ X(CIB_T) X(CIB_LR) X(IO_TYPE) +X(SLEWRATE) X(OSCA) diff --git a/nexus/fasm.cc b/nexus/fasm.cc index bb0a7941..4aaecdf4 100644 --- a/nexus/fasm.cc +++ b/nexus/fasm.cc @@ -433,7 +433,7 @@ struct NexusFasmWriter write_bit(stringf("BASE_TYPE.%s_%s", iodir, str_or_default(cell->attrs, id_IO_TYPE, "LVCMOS33").c_str())); write_ioattr(cell, "PULLMODE", "NONE"); write_ioattr(cell, "GLITCHFILTER", "OFF"); - write_ioattr(cell, "SLEWRATE", "MED"); + write_ioattr(cell, "SLEWRATE", str_or_default(cell->attrs, id_SLEWRATE, "MED").c_str()); write_cell_muxes(cell); pop(); } @@ -458,7 +458,7 @@ struct NexusFasmWriter const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR"); write_bit(stringf("BASE_TYPE.%s_%s", iodir, str_or_default(cell->attrs, id_IO_TYPE, "LVCMOS18H").c_str())); write_ioattr(cell, "PULLMODE", "NONE"); - write_ioattr(cell, "SLEWRATE", "MED"); + write_ioattr(cell, "SLEWRATE", str_or_default(cell->attrs, id_SLEWRATE, "MED").c_str()); pop(); write_cell_muxes(cell); pop(); diff --git a/nexus/pdc.cc b/nexus/pdc.cc index 67bab3c9..af622158 100644 --- a/nexus/pdc.cc +++ b/nexus/pdc.cc @@ -21,6 +21,7 @@ #include "log.h" #include "nextpnr.h" +#include <algorithm> #include <iterator> NEXTPNR_NAMESPACE_BEGIN @@ -423,6 +424,12 @@ struct PDCParser if (eqp == std::string::npos) log_error("expected key-value pair separated by '=' (line %d)", lineno); std::string k = kv.substr(0, eqp), v = kv.substr(eqp + 1); + if (k == "SLEWRATE") { + std::vector<std::string> slewrate_allowed = {"SLOW", "MED", "FAST", "NA"}; + if (std::find(std::begin(slewrate_allowed), std::end(slewrate_allowed), v) == + std::end(slewrate_allowed)) + log_error("unexpected SLEWRATE configuration %s (line %d)\n", v.c_str(), lineno); + } args[ctx->id(k)] = v; } } else { |