diff --git a/common/kernel/util.h b/common/kernel/util.h index f04a956bd5..0223470aff 100644 --- a/common/kernel/util.h +++ b/common/kernel/util.h @@ -102,6 +102,51 @@ bool bool_or_default(const Container &ct, const KeyType &key, bool def = false) return bool(int_or_default(ct, key, int(def))); }; +// Get a bool from a map-style container, returning default if value is not found +// Also tolerate string representation of boolean for interoperability purposes +template +bool boolstr_or_default(const dict &ct, const KeyType &key, bool def = false) +{ + auto found = ct.find(key); + if (found == ct.end()) + return def; + if (!found->second.is_string) + bool(found->second.as_int64()); + const char* str = found->second.as_string().c_str(); + if(!strcmp(str, "0") || !strcasecmp(str, "false")) + return false; + else if(!strcmp(str, "1") || !strcasecmp(str, "true")) + return true; + else + log_error("Expecting bool-compatible value but got '%s'.\n", found->second.as_string().c_str()); + return false; +}; + +// Get a vector of bool from a map-style container, returning default if value is not found +// Also tolerate string representation of vector for interoperability purposes +template +bool boolvec_populate(const dict &ct, const KeyType &key, std::vector& vec) +{ + auto found = ct.find(key); + if (found == ct.end()) + return false; + if (!found->second.is_string) + { + size_t val = found->second.as_int64(); + for (size_t i = 0; i < vec.size(); ++i, val>>=1) { + vec[i] = (val & 0x1) != 0; + } + } + else { + const std::string& str = found->second.as_string(); + size_t i = 0; + for (auto it = str.crbegin(); it != str.crend() && i < vec.size(); ++i, ++it) { + vec[i] = *it == '1'; + } + } + return true; +}; + // Get only value from a forward iterator begin/end pair. // // Generates assertion failure if std::distance(begin, end) != 1. diff --git a/common/place/placer_heap.cc b/common/place/placer_heap.cc index b701c65f40..132da8b972 100644 --- a/common/place/placer_heap.cc +++ b/common/place/placer_heap.cc @@ -301,30 +301,43 @@ class HeAPPlacer ++iter; } - // Apply saved solution + // Remove any previous binding for (auto &sc : solution) { CellInfo *cell = std::get<0>(sc); if (cell->bel != BelId()) ctx->unbindBel(cell->bel); } + // Apply saved solution for (auto &sc : solution) { CellInfo *cell; BelId bel; PlaceStrength strength; std::tie(cell, bel, strength) = sc; + // Just skip unbound cells here, these errors are handled just after + if (bel == BelId()) + continue; ctx->bindBel(bel, cell, strength); } + // Find and display all errors to help in finding the root cause of issues + unsigned num_errors = 0; for (auto &cell : ctx->cells) { if (cell.second->isPseudo()) continue; - if (cell.second->bel == BelId()) - log_error("Found unbound cell %s\n", cell.first.c_str(ctx)); - if (ctx->getBoundBelCell(cell.second->bel) != cell.second.get()) - log_error("Found cell %s with mismatched binding\n", cell.first.c_str(ctx)); - if (ctx->debug) + if (cell.second->bel == BelId()) { + log_nonfatal_error("Found unbound cell '%s' of type '%s'\n", cell.first.c_str(ctx), cell.second->type.c_str(ctx)); + num_errors++; + } + else if (ctx->getBoundBelCell(cell.second->bel) != cell.second.get()) { + log_nonfatal_error("Found mismatched binding for '%s' or type '%s'\n", cell.first.c_str(ctx), cell.second->type.c_str(ctx)); + num_errors++; + } + else if (ctx->debug) log_info("AP soln: %s -> %s\n", cell.first.c_str(ctx), ctx->nameOfBel(cell.second->bel)); } + if (num_errors > 0) { + log_error("Stopping the program after %u errors found\n", num_errors); + } bool any_bad_placements = false; for (auto bel : ctx->getBels()) { @@ -886,10 +899,10 @@ class HeAPPlacer while (!placed) { if (cfg.cell_placement_timeout > 0 && total_iters_for_cell > cfg.cell_placement_timeout) - log_error("Unable to find legal placement for cell '%s' after %d attempts, check constraints and " + log_error("Unable to find legal placement for cell '%s' of type '%s' after %d attempts, check constraints and " "utilisation. Use `--placer-heap-cell-placement-timeout` to change the number of " "attempts.\n", - ctx->nameOf(ci), total_iters_for_cell); + ctx->nameOf(ci), ci->type.c_str(ctx), total_iters_for_cell); // Determine a search radius around the solver location (which increases over time) that is clamped to // the region constraint for the cell (if applicable) diff --git a/himbaechel/uarch/xilinx/constids.inc b/himbaechel/uarch/xilinx/constids.inc index d4db03b2da..7e5024b3d1 100644 --- a/himbaechel/uarch/xilinx/constids.inc +++ b/himbaechel/uarch/xilinx/constids.inc @@ -288,6 +288,8 @@ X(BUFMRCE) X(C) X(CARRYIN) X(CARRY_TYPE) +X(CARRYCASCIN) +X(CARRYCASCOUT) X(CB) X(CDDCREQ) X(CE0) @@ -531,6 +533,8 @@ X(MMCME3_ADV) X(MMCME3_BASE) X(MMCME4_ADV) X(MMCME4_BASIC) +X(MULTSIGNIN) +X(MULTSIGNOUT) X(MUXCY) X(MUXF7) X(MUXF8) diff --git a/himbaechel/uarch/xilinx/fasm.cc b/himbaechel/uarch/xilinx/fasm.cc index 2c9dced7a0..f59a3fe469 100644 --- a/himbaechel/uarch/xilinx/fasm.cc +++ b/himbaechel/uarch/xilinx/fasm.cc @@ -1536,8 +1536,9 @@ struct FasmBackend if (binput == "CASCADE") write_bit("B_INPUT[0]"); - auto use_dport = str_or_default(ci->params, ctx->id("USE_DPORT"), "FALSE"); - if (use_dport == "TRUE") + // Tolerate both int and string types for interoperability purposes + auto use_dport = boolstr_or_default(ci->params, ctx->id("USE_DPORT"), false); + if (use_dport == true) write_bit("USE_DPORT[0]"); auto use_simd = str_or_default(ci->params, ctx->id("USE_SIMD"), "ONE48"); @@ -1547,14 +1548,10 @@ struct FasmBackend write_bit("USE_SIMD_FOUR12"); // PATTERN - auto pattern_str = str_or_default(ci->params, ctx->id("PATTERN"), ""); - if (!boost::empty(pattern_str)) { - const size_t pattern_size = 48; - std::vector pattern_vector(pattern_size, true); - size_t i = 0; - for (auto it = pattern_str.crbegin(); it != pattern_str.crend() && i < pattern_size; ++i, ++it) { - pattern_vector[i] = *it == '1'; - } + const size_t pattern_size = 48; + std::vector pattern_vector(pattern_size, false); + bool pattern_found = boolvec_populate(ci->params, ctx->id("PATTERN"), pattern_vector); + if (pattern_found) { write_vector("PATTERN[47:0]", pattern_vector); } @@ -1565,16 +1562,15 @@ struct FasmBackend write_bit("AUTORESET_PATDET_RESET_NOT_MATCH"); // MASK - auto mask_str = str_or_default(ci->params, ctx->id("MASK"), "001111111111111111111111111111111111111111111111"); // Yosys gives us 48 bit, but prjxray only recognizes 46 bits // The most significant two bits seem to be zero, so let us just truncate them - const size_t mask_size = 46; + const size_t mask_size = 48; std::vector mask_vector(mask_size, true); - size_t i = 0; - for (auto it = mask_str.crbegin(); it != mask_str.crend() && i < mask_size; ++i, ++it) { - mask_vector[i] = *it == '1'; + bool mask_found = boolvec_populate(ci->params, ctx->id("MASK"), mask_vector); + if (mask_found) { + mask_vector.resize(46); + write_vector("MASK[45:0]", mask_vector); } - write_vector("MASK[45:0]", mask_vector); auto sel_mask = str_or_default(ci->params, ctx->id("SEL_MASK"), "MASK"); if (sel_mask == "C") @@ -1599,7 +1595,7 @@ struct FasmBackend write_bit("ZMREG[0]", !bool_or_default(ci->params, ctx->id("MREG"))); write_bit("ZOPMODEREG[0]", !bool_or_default(ci->params, ctx->id("OPMODEREG"))); write_bit("ZPREG[0]", !bool_or_default(ci->params, ctx->id("PREG"))); - write_bit("USE_DPORT[0]", str_or_default(ci->params, ctx->id("USE_DPORT"), "FALSE") == "TRUE"); + write_bit("USE_DPORT[0]", boolstr_or_default(ci->params, ctx->id("USE_DPORT"), false)); write_bit("ZIS_CLK_INVERTED", !bool_or_default(ci->params, ctx->id("IS_CLK_INVERTED"))); write_bit("ZIS_CARRYIN_INVERTED", !bool_or_default(ci->params, ctx->id("IS_CARRYIN_INVERTED"))); pop(2); diff --git a/himbaechel/uarch/xilinx/pack.cc b/himbaechel/uarch/xilinx/pack.cc index a40ccbd387..a219fbf46c 100644 --- a/himbaechel/uarch/xilinx/pack.cc +++ b/himbaechel/uarch/xilinx/pack.cc @@ -742,7 +742,7 @@ void XilinxImpl::pack() packer.pack_luts(); packer.pack_dram(); packer.pack_bram(); - // packer.pack_dsps(); + packer.pack_dsps(); packer.pack_ffs(); packer.finalise_muxfs(); packer.pack_lutffs(); diff --git a/himbaechel/uarch/xilinx/pack.h b/himbaechel/uarch/xilinx/pack.h index 130949581e..c0579419f3 100644 --- a/himbaechel/uarch/xilinx/pack.h +++ b/himbaechel/uarch/xilinx/pack.h @@ -210,7 +210,7 @@ struct XC7Packer : public XilinxPacker void pack_dsps(); private: - void walk_dsp(CellInfo *root, CellInfo *ci, int constr_z); + unsigned walk_dsp(CellInfo *root, CellInfo *ci, int constr_z); void check_valid_pad(CellInfo *ci, std::string type); }; diff --git a/himbaechel/uarch/xilinx/pack_dsp_xc7.cc b/himbaechel/uarch/xilinx/pack_dsp_xc7.cc new file mode 100644 index 0000000000..a97c92ccf3 --- /dev/null +++ b/himbaechel/uarch/xilinx/pack_dsp_xc7.cc @@ -0,0 +1,195 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 Myrtle Shah + * Copyright (C) 2023 Hans Baier + * + * 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 +#include "pack.h" + +#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" +#include "himbaechel_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +static bool is_cascade_input(const PortInfo& port, const Context *ctx) +{ + if(port.name == id_CARRYCASCIN || port.name == id_MULTSIGNIN) return true; + const std::string& str = port.name.c_str(ctx); + return boost::starts_with(str, "ACIN") || boost::starts_with(str, "BCIN") || boost::starts_with(str, "PCIN"); +} +static bool is_cascade_output(const PortInfo& port, const Context *ctx) +{ + if(port.name == id_CARRYCASCOUT || port.name == id_MULTSIGNOUT) return true; + const std::string& str = port.name.c_str(ctx); + return boost::starts_with(str, "ACOUT") || boost::starts_with(str, "BCOUT") || boost::starts_with(str, "PCOUT"); +} + +// Return : the number of DSP marked as cascaded +unsigned XC7Packer::walk_dsp(CellInfo *root, CellInfo *current_cell, int constr_z) +{ + CellInfo *cascaded_cell = nullptr; + unsigned num_casc = 0; + + auto check_illegal_fanout = [&] (NetInfo *ni, std::string port) { + if (ni->users.entries() > 1) + log_error("Port %s connected to net %s has more than one user", port.c_str(), ni->name.c_str(ctx)); + + PortRef& user = *ni->users.begin(); + if (user.cell->type != id_DSP48E1_DSP48E1) + log_error("User %s of net %s is not a DSP block, but %s", + user.cell->name.c_str(ctx), ni->name.c_str(ctx), user.cell->type.c_str(ctx)); + }; + + // see if any cascade outputs are connected + for (auto port : current_cell->ports) { + if (!is_cascade_output(port.second, ctx)) continue; + NetInfo *cout_net = port.second.net; + + if (cout_net == nullptr || cout_net->users.empty()) continue; + + check_illegal_fanout(cout_net, port.first.c_str(ctx)); + PortRef& user = *cout_net->users.begin(); + CellInfo *cout_cell = user.cell; + NPNR_ASSERT(cout_cell != nullptr); + + if (cascaded_cell != nullptr && cout_cell != cascaded_cell) + log_error("the cascading outputs of DSP block %s are connected to different cells", + current_cell->name.c_str(ctx)); + + cascaded_cell = cout_cell; + } + + if (cascaded_cell != nullptr) { + auto is_lower_bel = constr_z == BEL_LOWER_DSP; + + cascaded_cell->cluster = root->name; + root->constr_children.push_back(cascaded_cell); + cascaded_cell->constr_x = 0; + // The connected cell has to be above the current cell, + // otherwise it cannot be routed, because the cascading ports + // are only connected to the DSP above + auto previous_y = (current_cell == root) ? 0 : current_cell->constr_y; + cascaded_cell->constr_y = previous_y + (is_lower_bel ? -5 : 0); + cascaded_cell->constr_z = constr_z; + cascaded_cell->constr_abs_z = true; + + num_casc += 1; + num_casc += walk_dsp(root, cascaded_cell, is_lower_bel ? BEL_UPPER_DSP : BEL_LOWER_DSP); + } + + return num_casc; +} + +void XC7Packer::pack_dsps() +{ + log_info("Packing DSPs..\n"); + + dict dsp_rules; + dsp_rules[id_DSP48E1].new_type = id_DSP48E1_DSP48E1; + generic_xform(dsp_rules, true); + + std::vector all_dsps; + + // Clean connections of DSPs + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + + auto add_const_pin = [&](PortInfo& port, std::string& pins, std::string& pin_name, std::string net) { + if (port.net && port.net->name == ctx->id(net)) { + ci->disconnectPort(port.name); + pins += " " + pin_name; + } + }; + + if (ci->type == id_DSP48E1_DSP48E1) { + all_dsps.push_back(ci); + auto gnd_attr = ctx->id("DSP_GND_PINS"); + auto vcc_attr = ctx->id("DSP_VCC_PINS"); + + auto gnd_pins = str_or_default(ci->attrs, gnd_attr, ""); + auto vcc_pins = str_or_default(ci->attrs, vcc_attr, ""); + + for (auto &port : ci->ports) { + std::string n = port.first.str(ctx); + + // Cascading inputs do not use routing resources, so disconnect them if constants + if (is_cascade_input(port.second, ctx)) { + if (port.second.net == nullptr) + continue; + if (port.second.net->name == ctx->id("$PACKER_GND_NET")) + ci->disconnectPort(port.first); + } + + // prjxray has extra bits for these ports to hardwire them to VCC/GND + // as these seem to be interal to the tile, + // this saves us from having to route those externally + if (boost::starts_with(n, "D") || + boost::starts_with(n, "RSTD") || + // TODO: these seem to be inverted for unknown reasons + // boost::starts_with(n, "INMODE") || + // boost::starts_with(n, "ALUMODE2") || + // boost::starts_with(n, "ALUMODE3") || + boost::starts_with(n, "CARRYINSEL2") || + boost::starts_with(n, "CED") || + boost::starts_with(n, "CEAD") || + boost::starts_with(n, "CEINMODE") || + boost::starts_with(n, "CEALUMODE")) { + add_const_pin(port.second, gnd_pins, n, "$PACKER_GND_NET"); + add_const_pin(port.second, vcc_pins, n, "$PACKER_VCC_NET"); + } + } + + ci->attrs[gnd_attr] = gnd_pins; + ci->attrs[vcc_attr] = vcc_pins; + } + } + + // Find the roots of cascaded DSP + std::vector dsp_roots; + for (auto ci : all_dsps) { + bool cascade_input_used = false; + for (auto port : ci->ports) { + if (!is_cascade_input(port.second, ctx)) continue; + if (port.second.net != nullptr) { + cascade_input_used = true; + break; + } + } + + if (!cascade_input_used) { + dsp_roots.push_back(ci); + } + } + + // Create clusters of cascaded DSPs + unsigned num_casc = 0; + for (auto root : dsp_roots) { + root->constr_abs_z = true; + root->constr_z = BEL_LOWER_DSP; + unsigned loc_casc = walk_dsp(root, root, BEL_UPPER_DSP); + if(loc_casc > 0) { + root->cluster = root->name; + } + num_casc += loc_casc; + } + if(num_casc > 0) { + log_info("Found %u cascaded DSP from %u roots\n", num_casc, (unsigned)dsp_roots.size()); + } +} + +NEXTPNR_NAMESPACE_END