diff --git a/common/constraints.h b/common/constraints.h new file mode 100644 index 00000000..dfb108f8 --- /dev/null +++ b/common/constraints.h @@ -0,0 +1,65 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 The SymbiFlow Authors. + * + * 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 CONSTRAINTS_H +#define CONSTRAINTS_H + +#ifndef NEXTPNR_H +#error Include after "nextpnr.h" only. +#endif + +#include "exclusive_state_groups.h" + +NEXTPNR_NAMESPACE_BEGIN + +template struct Constraints +{ + using ConstraintStateType = StateType; + using ConstraintCountType = CountType; + + enum ConstraintType + { + CONSTRAINT_TAG_IMPLIES = 0, + CONSTRAINT_TAG_REQUIRES = 1, + }; + + template struct Constraint + { + virtual size_t tag() const = 0; + virtual ConstraintType constraint_type() const = 0; + virtual StateType state() const = 0; + virtual StateRange states() const = 0; + }; + + typedef ExclusiveStateGroup TagState; + std::unordered_map> definitions; + + template void bindBel(TagState *tags, const ConstraintRange constraints); + + template void unbindBel(TagState *tags, const ConstraintRange constraints); + + template + bool isValidBelForCellType(const Context *ctx, uint32_t prototype, const TagState *tags, + const ConstraintRange constraints, IdString object, IdString cell, BelId bel, + bool explain_constraints) const; +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/constraints.impl.h b/common/constraints.impl.h new file mode 100644 index 00000000..9c978411 --- /dev/null +++ b/common/constraints.impl.h @@ -0,0 +1,109 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 The SymbiFlow Authors. + * + * 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 CONSTRAINTS_IMPL_H +#define CONSTRAINTS_IMPL_H + +#include "exclusive_state_groups.impl.h" + +NEXTPNR_NAMESPACE_BEGIN + +template +template +void Constraints::bindBel(TagState *tags, const ConstraintRange constraints) +{ + for (const auto &constraint : constraints) { + switch (constraint.constraint_type()) { + case CONSTRAINT_TAG_IMPLIES: + tags[constraint.tag()].add_implies(constraint.state()); + break; + case CONSTRAINT_TAG_REQUIRES: + break; + default: + NPNR_ASSERT(false); + } + } +} + +template +template +void Constraints::unbindBel(TagState *tags, const ConstraintRange constraints) +{ + for (const auto &constraint : constraints) { + switch (constraint.constraint_type()) { + case CONSTRAINT_TAG_IMPLIES: + tags[constraint.tag()].remove_implies(constraint.state()); + break; + case CONSTRAINT_TAG_REQUIRES: + break; + default: + NPNR_ASSERT(false); + } + } +} + +template +template +bool Constraints::isValidBelForCellType(const Context *ctx, uint32_t prototype, + const TagState *tags, + const ConstraintRange constraints, + IdString object, IdString cell, BelId bel, + bool explain_constraints) const +{ + if (explain_constraints) { + auto &state_definition = definitions.at(prototype); + for (const auto &constraint : constraints) { + switch (constraint.constraint_type()) { + case CONSTRAINT_TAG_IMPLIES: + tags[constraint.tag()].explain_implies(ctx, object, cell, state_definition.at(constraint.tag()), bel, + constraint.state()); + break; + case CONSTRAINT_TAG_REQUIRES: + tags[constraint.tag()].explain_requires(ctx, object, cell, state_definition.at(constraint.tag()), bel, + constraint.states()); + break; + default: + NPNR_ASSERT(false); + } + } + } + + for (const auto &constraint : constraints) { + switch (constraint.constraint_type()) { + case CONSTRAINT_TAG_IMPLIES: + if (!tags[constraint.tag()].check_implies(constraint.state())) { + return false; + } + break; + case CONSTRAINT_TAG_REQUIRES: + if (!tags[constraint.tag()].requires(constraint.states())) { + return false; + } + break; + default: + NPNR_ASSERT(false); + } + } + + return true; +} + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/exclusive_state_groups.h b/common/exclusive_state_groups.h new file mode 100644 index 00000000..548dfe4b --- /dev/null +++ b/common/exclusive_state_groups.h @@ -0,0 +1,146 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 The SymbiFlow Authors. + * + * 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 EXCLUSIVE_STATE_GROUPS_H +#define EXCLUSIVE_STATE_GROUPS_H + +#ifndef NEXTPNR_H +#error Include after "nextpnr.h" only. +#endif + +NEXTPNR_NAMESPACE_BEGIN + +// Implementation for exclusive state groups, used to implement generic +// constraint system. +template struct ExclusiveStateGroup +{ + ExclusiveStateGroup() : state(kNoSelected) { count.fill(0); } + struct Definition + { + IdString prefix; + IdString default_state; + std::vector states; + }; + + static_assert(StateCount < std::numeric_limits::max(), "StateType cannot store max StateType"); + static_assert(std::numeric_limits::is_signed, "StateType must be signed"); + + std::bitset selected_states; + StateType state; + std::array count; + + static constexpr StateType kNoSelected = -1; + static constexpr StateType kOverConstrained = -2; + + std::pair current_state(const Definition &definition) const + { + if (state <= 0) { + return std::make_pair(state == kNoSelected, definition.default_state); + } else { + NPNR_ASSERT(state <= definition.states.size()); + return std::make_pair(true, definition.states[state]); + } + } + + bool check_implies(int32_t next_state) const + { + // Implies can be satified if either that state is + // selected, or no state is currently selected. + return state == next_state || state == kNoSelected; + } + + bool add_implies(int32_t next_state) + { + NPNR_ASSERT(next_state < StateCount); + + // Increment and mark the state as selected. + count[next_state] += 1; + selected_states[next_state] = true; + + if (state == next_state) { + // State was already selected, state group is still satified. + return true; + } else if (selected_states.count() == 1) { + // State was not select selected, state is now selected. + // State group is satified. + state = next_state; + return true; + } else { + // State group is now overconstrained. + state = kOverConstrained; + return false; + } + }; + + void remove_implies(int32_t next_state) + { + NPNR_ASSERT(next_state < StateCount); + NPNR_ASSERT(selected_states[next_state]); + + count[next_state] -= 1; + NPNR_ASSERT(count[next_state] >= 0); + + // Check if next_state is now unselected. + if (count[next_state] == 0) { + // next_state is not longer selected + selected_states[next_state] = false; + + // Check whether the state group is now unselected or satified. + auto value = selected_states.to_ulong(); + auto number_selected = __builtin_popcount(value); + if (number_selected == 1) { + // Group is no longer overconstrained. + state = __builtin_ctz(value); + NPNR_ASSERT(selected_states[state]); + } else if (number_selected == 0) { + // Group is unselected. + state = kNoSelected; + } else { + state = kOverConstrained; + } + } + } + + template bool requires(const StateRange &state_range) const + { + if (state < 0) { + return false; + } + + for (const auto required_state : state_range) { + if (state == required_state) { + return true; + } + } + + return false; + } + + void print_debug(const Context *ctx, IdString object, const Definition &definition) const; + void explain_implies(const Context *ctx, IdString object, IdString cell, const Definition &definition, BelId bel, + int32_t next_state) const; + + template + void explain_requires(const Context *ctx, IdString object, IdString cell, const Definition &definition, BelId bel, + const StateRange state_range) const; +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/exclusive_state_groups.impl.h b/common/exclusive_state_groups.impl.h new file mode 100644 index 00000000..864e16c6 --- /dev/null +++ b/common/exclusive_state_groups.impl.h @@ -0,0 +1,96 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 The SymbiFlow Authors. + * + * 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. + * + */ + +#pragma once + +#include "nextpnr.h" + +// This header must be included after "nextpnr.h", otherwise circular header +// import insanity occurs. +#ifndef NEXTPNR_H_COMPLETE +#error This header cannot be used until after "nextpnr.h" is included +#endif + +#include "log.h" + +NEXTPNR_NAMESPACE_BEGIN + +template +void ExclusiveStateGroup::print_debug(const Context *ctx, IdString object, + const Definition &definition) const +{ + if (state == kNoSelected) { + NPNR_ASSERT(selected_states.count() == 0); + log_info("%s.%s is currently unselected\n", object.c_str(ctx), definition.prefix.c_str(ctx)); + } else if (state >= 0) { + log_info("%s.%s = %s, count = %d\n", object.c_str(ctx), definition.prefix.c_str(ctx), + definition.states[state].c_str(ctx), count[state]); + } else { + NPNR_ASSERT(state == kOverConstrained); + log_info("%s.%s is currently overconstrained, states selected:\n", object.c_str(ctx), + definition.prefix.c_str(ctx)); + for (size_t i = 0; i < definition.states.size(); ++i) { + if (selected_states[i]) { + log_info(" - %s, count = %d\n", definition.states[i].c_str(ctx), count[i]); + } + } + } +} + +template +void ExclusiveStateGroup::explain_implies(const Context *ctx, IdString object, + IdString cell, const Definition &definition, + BelId bel, int32_t next_state) const +{ + if (check_implies(next_state)) { + log_info("Placing cell %s at bel %s does not violate %s.%s\n", cell.c_str(ctx), ctx->nameOfBel(bel), + object.c_str(ctx), definition.prefix.c_str(ctx)); + } else { + NPNR_ASSERT(next_state < definition.states.size()); + log_info("Placing cell %s at bel %s does violates %s.%s.\n", cell.c_str(ctx), ctx->nameOfBel(bel), + object.c_str(ctx), definition.prefix.c_str(ctx)); + print_debug(ctx, object, definition); + } +} + +template +template +void ExclusiveStateGroup::explain_requires(const Context *ctx, IdString object, + IdString cell, + const Definition &definition, BelId bel, + const StateRange state_range) const +{ + if (requires(state_range)) { + log_info("Placing cell %s at bel %s does not violate %s.%s\n", cell.c_str(ctx), ctx->nameOfBel(bel), + object.c_str(ctx), definition.prefix.c_str(ctx)); + } else { + log_info("Placing cell %s at bel %s does violates %s.%s, because current state is %s, constraint requires one " + "of:\n", + cell.c_str(ctx), ctx->nameOfBel(bel), object.c_str(ctx), definition.prefix.c_str(ctx), + definition.states[state].c_str(ctx)); + + for (const auto required_state : state_range) { + NPNR_ASSERT(required_state < definition.states.size()); + log_info(" - %s\n", definition.states[required_state].c_str(ctx)); + } + print_debug(ctx, object, definition); + } +} + +NEXTPNR_NAMESPACE_END diff --git a/common/nextpnr.h b/common/nextpnr.h index 4ddf8fef..f2bcb90d 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -1559,4 +1559,6 @@ struct Context : Arch, DeterministicRNG NEXTPNR_NAMESPACE_END +#define NEXTPNR_H_COMPLETE + #endif diff --git a/fpga_interchange/arch.cc b/fpga_interchange/arch.cc index c24ec660..41659861 100644 --- a/fpga_interchange/arch.cc +++ b/fpga_interchange/arch.cc @@ -25,6 +25,7 @@ #include #include #include +#include "constraints.impl.h" #include "fpga_interchange.h" #include "log.h" #include "nextpnr.h" @@ -89,11 +90,8 @@ Arch::Arch(ArchArgs args) : args(args) log_error("Unable to read chipdb %s\n", args.chipdb.c_str()); } - tileStatus.resize(chip_info->tiles.size()); - for (int i = 0; i < chip_info->tiles.ssize(); i++) { - tileStatus[i].boundcells.resize(chip_info->tile_types[chip_info->tiles[i].type].bel_data.size()); - } - + // Read strings from constids into IdString database, checking that list + // is unique and matches expected constid value. const RelSlice> &constids = *chip_info->constids; for (size_t i = 0; i < constids.size(); ++i) { IdString::initialize_add(this, constids[i].get(), i + 1); @@ -155,6 +153,30 @@ Arch::Arch(ArchArgs args) : args(args) pads.emplace(bel); } } + + explain_constraints = false; + + int tile_type_index = 0; + size_t max_tag_count = 0; + for (const TileTypeInfoPOD &tile_type : chip_info->tile_types) { + max_tag_count = std::max(max_tag_count, tile_type.tags.size()); + + auto &type_definition = constraints.definitions[tile_type_index]; + for (const ConstraintTagPOD &tag : tile_type.tags) { + type_definition.emplace_back(); + auto &definition = type_definition.back(); + definition.prefix = IdString(tag.tag_prefix); + definition.default_state = IdString(tag.default_state); + NPNR_ASSERT(tag.states.size() < kMaxState); + + definition.states.reserve(tag.states.size()); + for (auto state : tag.states) { + definition.states.push_back(IdString(state)); + } + } + } + + default_tags.resize(max_tag_count); } // ----------------------------------------------------------------------- @@ -563,20 +585,55 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay bool Arch::pack() { - // FIXME: Implement this - return false; + pack_ports(); + return true; } bool Arch::place() { - // FIXME: Implement this - return false; + std::string placer = str_or_default(settings, id("placer"), defaultPlacer); + + if (placer == "heap") { + PlacerHeapCfg cfg(getCtx()); + cfg.criticalityExponent = 7; + cfg.alpha = 0.08; + cfg.beta = 0.4; + cfg.placeAllAtOnce = true; + cfg.hpwl_scale_x = 1; + cfg.hpwl_scale_y = 2; + cfg.spread_scale_x = 2; + cfg.spread_scale_y = 1; + cfg.solverTolerance = 0.6e-6; + if (!placer_heap(getCtx(), cfg)) + return false; + } else if (placer == "sa") { + if (!placer1(getCtx(), Placer1Cfg(getCtx()))) + return false; + } else { + log_error("FPGA interchange architecture does not support placer '%s'\n", placer.c_str()); + } + + getCtx()->attrs[getCtx()->id("step")] = std::string("place"); + archInfoToAttributes(); + return true; } bool Arch::route() { - // FIXME: Implement this - return false; + std::string router = str_or_default(settings, id("router"), defaultRouter); + + bool result; + if (router == "router1") { + result = router1(getCtx(), Router1Cfg(getCtx())); + } else if (router == "router2") { + router2(getCtx(), Router2Cfg(getCtx())); + result = true; + } else { + log_error("FPGA interchange architecture does not support router '%s'\n", router.c_str()); + } + getCtx()->attrs[getCtx()->id("step")] = std::string("route"); + archInfoToAttributes(); + return result; } // ----------------------------------------------------------------------- @@ -771,4 +828,12 @@ bool Arch::is_net_within_site(const NetInfo &net) const return true; } +// Instance constraint templates. +template void Arch::ArchConstraints::bindBel(Arch::ArchConstraints::TagState *, const Arch::ConstraintRange); +template void Arch::ArchConstraints::unbindBel(Arch::ArchConstraints::TagState *, const Arch::ConstraintRange); +template bool Arch::ArchConstraints::isValidBelForCellType(const Context *, uint32_t, + const Arch::ArchConstraints::TagState *, + const Arch::ConstraintRange, IdString, IdString, BelId, + bool) const; + NEXTPNR_NAMESPACE_END diff --git a/fpga_interchange/arch.h b/fpga_interchange/arch.h index 638855cd..88f35df4 100644 --- a/fpga_interchange/arch.h +++ b/fpga_interchange/arch.h @@ -28,6 +28,8 @@ #include +#include "constraints.h" + NEXTPNR_NAMESPACE_BEGIN /**** Everything in this section must be kept in sync with chipdb.py ****/ @@ -751,12 +753,14 @@ struct Arch : ArchAPI std::unordered_map> driving_pip_loc; std::unordered_map reserved_wires; + static constexpr size_t kMaxState = 8; struct TileStatus { + std::vector> tags; std::vector boundcells; }; - std::vector tileStatus; + std::unordered_map tileStatus; ArchArgs args; Arch(ArchArgs args); @@ -818,12 +822,24 @@ struct Arch : ArchAPI void map_cell_pins(CellInfo *cell, int32_t mapping) const; void map_port_pins(BelId bel, CellInfo *cell) const; + TileStatus &get_tile_status(int32_t tile) + { + auto result = tileStatus.emplace(tile, TileStatus()); + if (result.second) { + auto &tile_type = chip_info->tile_types[chip_info->tiles[tile].type]; + result.first->second.boundcells.resize(tile_type.bel_data.size()); + result.first->second.tags.resize(default_tags.size()); + } + + return result.first->second; + } + void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) override { NPNR_ASSERT(bel != BelId()); - NPNR_ASSERT(tileStatus[bel.tile].boundcells[bel.index] == nullptr); - tileStatus[bel.tile].boundcells[bel.index] = cell; + TileStatus &tile_status = get_tile_status(bel.tile); + NPNR_ASSERT(tile_status.boundcells[bel.index] == nullptr); const auto &bel_data = bel_info(chip_info, bel); NPNR_ASSERT(bel_data.category == BEL_CATEGORY_LOGIC); @@ -835,36 +851,69 @@ struct Arch : ArchAPI if (cell->cell_mapping != mapping) { map_cell_pins(cell, mapping); } + constraints.bindBel(tile_status.tags.data(), get_cell_constraints(bel, cell->type)); } else { map_port_pins(bel, cell); + // FIXME: Probably need to actually constraint io port cell/bel, + // but the current BBA emission doesn't support that. This only + // really matters if the placer can choose IO port locations. } + + tile_status.boundcells[bel.index] = cell; + cell->bel = bel; cell->belStrength = strength; + refreshUiBel(bel); } void unbindBel(BelId bel) override { NPNR_ASSERT(bel != BelId()); - NPNR_ASSERT(tileStatus[bel.tile].boundcells[bel.index] != nullptr); - tileStatus[bel.tile].boundcells[bel.index]->bel = BelId(); - tileStatus[bel.tile].boundcells[bel.index]->belStrength = STRENGTH_NONE; - tileStatus[bel.tile].boundcells[bel.index] = nullptr; + + TileStatus &tile_status = get_tile_status(bel.tile); + NPNR_ASSERT(tile_status.boundcells[bel.index] != nullptr); + + CellInfo *cell = tile_status.boundcells[bel.index]; + tile_status.boundcells[bel.index] = nullptr; + + cell->bel = BelId(); + cell->belStrength = STRENGTH_NONE; + + // FIXME: Probably need to actually constraint io port cell/bel, + // but the current BBA emission doesn't support that. This only + // really matters if the placer can choose IO port locations. + if (io_port_types.count(cell->type) == 0) { + constraints.unbindBel(tile_status.tags.data(), get_cell_constraints(bel, cell->type)); + } + refreshUiBel(bel); } - bool checkBelAvail(BelId bel) const override { return tileStatus[bel.tile].boundcells[bel.index] == nullptr; } + bool checkBelAvail(BelId bel) const override + { + // FIXME: This could consult the constraint system to see if this BEL + // is blocked (e.g. site type is wrong). + return getBoundBelCell(bel) == nullptr; + } CellInfo *getBoundBelCell(BelId bel) const override { NPNR_ASSERT(bel != BelId()); - return tileStatus[bel.tile].boundcells[bel.index]; + auto iter = tileStatus.find(bel.tile); + if (iter == tileStatus.end()) { + return nullptr; + } else { + return iter->second.boundcells[bel.index]; + } } CellInfo *getConflictingBelCell(BelId bel) const override { NPNR_ASSERT(bel != BelId()); - return tileStatus[bel.tile].boundcells[bel.index]; + // FIXME: This could consult the constraint system to see why this BEL + // is blocked. + return getBoundBelCell(bel); } BelRange getBels() const override @@ -1251,6 +1300,9 @@ struct Arch : ArchAPI // ------------------------------------------------- + void place_iobufs(WireId pad_wire, NetInfo *net, const std::unordered_set &tightly_attached_bels, + std::unordered_set *placed_cells); + void pack_ports(); bool pack() override; bool place() override; bool route() override; @@ -1352,16 +1404,40 @@ struct Arch : ArchAPI { if (io_port_types.count(cell_type)) { return pads.count(bel) > 0; - } else { - return bel_info(chip_info, bel).pin_map[get_cell_type_index(cell_type)] > 0; } + + auto cell_type_index = get_cell_type_index(cell_type); + const auto &bel_data = bel_info(chip_info, bel); + if (bel_data.category != BEL_CATEGORY_LOGIC) { + return false; + } + return bel_data.pin_map[cell_type_index] != -1; } // Return true whether all Bels at a given location are valid bool isBelLocationValid(BelId bel) const override { - // FIXME: Implement this - return true; + auto iter = tileStatus.find(bel.tile); + if (iter == tileStatus.end()) { + return true; + } + const TileStatus &tile_status = iter->second; + const CellInfo *cell = tile_status.boundcells[bel.index]; + if (cell == nullptr) { + return true; + } else { + if (io_port_types.count(cell->type)) { + // FIXME: Probably need to actually constraint io port cell/bel, + // but the current BBA emission doesn't support that. This only + // really matters if the placer can choose IO port locations. + return true; + } + + return constraints.isValidBelForCellType(getCtx(), get_constraint_prototype(bel), tile_status.tags.data(), + get_cell_constraints(bel, cell->type), + id(chip_info->tiles[bel.tile].name.get()), cell->name, bel, + explain_constraints); + } } IdString get_bel_tiletype(BelId bel) const { return IdString(loc_info(chip_info, bel).name); } @@ -1406,6 +1482,93 @@ struct Arch : ArchAPI // // Returns false if any element of the net is not placed. bool is_net_within_site(const NetInfo &net) const; + + using ArchConstraints = Constraints; + ArchConstraints constraints; + std::vector default_tags; + bool explain_constraints; + + struct StateRange + { + const int32_t *b; + const int32_t *e; + + const int32_t *begin() const { return b; } + const int32_t *end() const { return e; } + }; + + struct Constraint : ArchConstraints::Constraint + { + const CellConstraintPOD *constraint; + Constraint(const CellConstraintPOD *constraint) : constraint(constraint) {} + + size_t tag() const override { return constraint->tag; } + + ArchConstraints::ConstraintType constraint_type() const override + { + return Constraints::ConstraintType(constraint->constraint_type); + } + + ArchConstraints::ConstraintStateType state() const override + { + NPNR_ASSERT(constraint_type() == Constraints::CONSTRAINT_TAG_IMPLIES); + NPNR_ASSERT(constraint->states.size() == 1); + return constraint->states[0]; + } + + StateRange states() const override + { + StateRange range; + range.b = constraint->states.get(); + range.e = range.b + constraint->states.size(); + + return range; + } + }; + + struct ConstraintIterator + { + const CellConstraintPOD *constraint; + ConstraintIterator() {} + + ConstraintIterator operator++() + { + ++constraint; + return *this; + } + + bool operator!=(const ConstraintIterator &other) const { return constraint != other.constraint; } + + bool operator==(const ConstraintIterator &other) const { return constraint == other.constraint; } + + Constraint operator*() const { return Constraint(constraint); } + }; + + struct ConstraintRange + { + ConstraintIterator b, e; + + ConstraintIterator begin() const { return b; } + ConstraintIterator end() const { return e; } + }; + + uint32_t get_constraint_prototype(BelId bel) const { return chip_info->tiles[bel.tile].type; } + + ConstraintRange get_cell_constraints(BelId bel, IdString cell_type) const + { + const auto &bel_data = bel_info(chip_info, bel); + NPNR_ASSERT(bel_data.category == BEL_CATEGORY_LOGIC); + + int32_t mapping = bel_data.pin_map[get_cell_type_index(cell_type)]; + NPNR_ASSERT(mapping >= 0); + + auto &cell_bel_map = chip_info->cell_map->cell_bel_map[mapping]; + ConstraintRange range; + range.b.constraint = cell_bel_map.constraints.get(); + range.e.constraint = range.b.constraint + cell_bel_map.constraints.size(); + + return range; + } }; NEXTPNR_NAMESPACE_END diff --git a/fpga_interchange/arch_pack_io.cc b/fpga_interchange/arch_pack_io.cc new file mode 100644 index 00000000..cc1cfb93 --- /dev/null +++ b/fpga_interchange/arch_pack_io.cc @@ -0,0 +1,215 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 Symbiflow Authors + * + * + * 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" + +NEXTPNR_NAMESPACE_BEGIN + +void Arch::place_iobufs(WireId pad_wire, NetInfo *net, const std::unordered_set &tightly_attached_bels, + std::unordered_set *placed_cells) +{ + for (BelPin bel_pin : getWireBelPins(pad_wire)) { + BelId bel = bel_pin.bel; + for (CellInfo *cell : tightly_attached_bels) { + if (isValidBelForCellType(cell->type, bel)) { + NPNR_ASSERT(cell->bel == BelId()); + NPNR_ASSERT(placed_cells->count(cell) == 0); + + bindBel(bel, cell, STRENGTH_FIXED); + placed_cells->emplace(cell); + + IdString cell_port; + for (auto pin_pair : cell->cell_bel_pins) { + for (IdString a_bel_pin : pin_pair.second) { + if (a_bel_pin == bel_pin.pin) { + NPNR_ASSERT(cell_port == IdString()); + cell_port = pin_pair.first; + } + } + } + NPNR_ASSERT(cell_port != IdString()); + + const PortInfo &port = cell->ports.at(cell_port); + NPNR_ASSERT(port.net == net); + } + } + } +} + +void Arch::pack_ports() +{ + std::unordered_map tile_type_prototypes; + for (size_t i = 0; i < chip_info->tiles.size(); ++i) { + const auto &tile = chip_info->tiles[i]; + const auto &tile_type = chip_info->tile_types[tile.type]; + IdString tile_type_name(tile_type.name); + tile_type_prototypes.emplace(tile_type_name, &tile); + } + + // set(site_types) for package pins + std::unordered_set package_sites; + // Package pin -> (Site type -> BelId) + std::unordered_map> package_pin_bels; + for (const PackagePinPOD &package_pin : chip_info->packages[package_index].pins) { + IdString pin(package_pin.package_pin); + IdString bel(package_pin.bel); + + IdString site(package_pin.site); + package_sites.emplace(site); + + for (size_t i = 0; i < chip_info->tiles.size(); ++i) { + const auto &tile = chip_info->tiles[i]; + std::unordered_set package_pin_sites; + for (size_t j = 0; j < tile.sites.size(); ++j) { + auto &site_data = chip_info->sites[tile.sites[j]]; + if (site == id(site_data.site_name.get())) { + package_pin_sites.emplace(j); + } + } + + const auto &tile_type = chip_info->tile_types[tile.type]; + for (size_t j = 0; j < tile_type.bel_data.size(); ++j) { + const BelInfoPOD &bel_data = tile_type.bel_data[j]; + if (bel == IdString(bel_data.name) && package_pin_sites.count(bel_data.site)) { + auto &site_data = chip_info->sites[tile.sites[bel_data.site]]; + IdString site_type(site_data.site_type); + BelId bel; + bel.tile = i; + bel.index = j; + package_pin_bels[pin][site_type] = bel; + } + } + } + } + + // Determine for each package site type, which site types are possible. + std::unordered_set package_pin_site_types; + std::unordered_map> possible_package_site_types; + for (const TileInstInfoPOD &tile : chip_info->tiles) { + for (size_t site_index : tile.sites) { + const SiteInstInfoPOD &site = chip_info->sites[site_index]; + IdString site_name = getCtx()->id(site.site_name.get()); + if (package_sites.count(site_name) == 1) { + possible_package_site_types[site_name].emplace(IdString(site.site_type)); + package_pin_site_types.emplace(IdString(site.site_type)); + } + } + } + + // IO sites are usually pretty weird, so see if we can define some + // constraints between the port cell create by nextpnr and cells that are + // immediately attached to that port cell. + for (auto port_pair : port_cells) { + CellInfo *port_cell = port_pair.second; + std::unordered_set tightly_attached_bels; + + for (auto port_pair : port_cell->ports) { + const PortInfo &port_info = port_pair.second; + const NetInfo *net = port_info.net; + if (net->driver.cell) { + tightly_attached_bels.emplace(net->driver.cell); + } + + for (const PortRef &port_ref : net->users) { + if (port_ref.cell) { + tightly_attached_bels.emplace(port_ref.cell); + } + } + } + + NPNR_ASSERT(tightly_attached_bels.erase(port_cell) == 1); + std::unordered_set cell_types_in_io_group; + for (CellInfo *cell : tightly_attached_bels) { + NPNR_ASSERT(port_cells.find(cell->name) == port_cells.end()); + cell_types_in_io_group.emplace(cell->type); + } + + // Get possible placement locations for tightly coupled BELs with + // port. + std::unordered_set possible_site_types; + for (const TileTypeInfoPOD &tile_type : chip_info->tile_types) { + IdString tile_type_name(tile_type.name); + for (const BelInfoPOD &bel_info : tile_type.bel_data) { + for (IdString cell_type : cell_types_in_io_group) { + size_t cell_type_index = get_cell_type_index(cell_type); + if (bel_info.category == BEL_CATEGORY_LOGIC && bel_info.pin_map[cell_type_index] != -1) { + auto *tile = tile_type_prototypes.at(tile_type_name); + const SiteInstInfoPOD &site = chip_info->sites[tile->sites[bel_info.site]]; + + IdString site_type(site.site_type); + if (package_pin_site_types.count(site_type)) { + possible_site_types.emplace(site_type); + } + } + } + } + } + + auto iter = port_cell->attrs.find(id("PACKAGE_PIN")); + if (iter == port_cell->attrs.end()) { + // FIXME: Relax this constraint + log_error("Port '%s' is missing PACKAGE_PIN property\n", port_cell->name.c_str(getCtx())); + } + + // std::unordered_map> package_pin_bels; + IdString package_pin_id = id(iter->second.as_string()); + auto pin_iter = package_pin_bels.find(package_pin_id); + if (pin_iter == package_pin_bels.end()) { + log_error("Package pin '%s' not found in part %s\n", package_pin_id.c_str(getCtx()), get_part().c_str()); + } + NPNR_ASSERT(pin_iter != package_pin_bels.end()); + + BelId package_bel; + for (IdString site_type : possible_site_types) { + auto site_iter = pin_iter->second.find(site_type); + if (site_iter != pin_iter->second.end()) { + // FIXME: Need to handle case where a port can be in multiple + // modes, but only one of the modes works. + // + // NPNR_ASSERT(package_bel == BelId()); + package_bel = site_iter->second; + } + } + NPNR_ASSERT(package_bel != BelId()); + + std::unordered_set placed_cells; + bindBel(package_bel, port_cell, STRENGTH_FIXED); + placed_cells.emplace(port_cell); + + IdStringRange package_bel_pins = getBelPins(package_bel); + // NPNR_ASSERT(std::distance(package_bel_pins.begin(), package_bel_pins.end()) == 1); + IdStringIterator b = package_bel_pins.begin(); + NPNR_ASSERT(b != package_bel_pins.end()); + ++b; + NPNR_ASSERT(b == package_bel_pins.end()); + IdString pad_pin = *package_bel_pins.begin(); + + WireId pad_wire = getBelPinWire(package_bel, pad_pin); + place_iobufs(pad_wire, ports[port_pair.first].net, tightly_attached_bels, &placed_cells); + + for (CellInfo *cell : placed_cells) { + NPNR_ASSERT(cell->bel != BelId()); + NPNR_ASSERT(isValidBelForCell(cell, cell->bel)); + } + } +} + +NEXTPNR_NAMESPACE_END