Add initial logic for handling dedicated interconnect situations.
Signed-off-by: Keith Rothman <537074+litghost@users.noreply.github.com>
This commit is contained in:
parent
cd8297f54d
commit
2fc353d559
@ -195,6 +195,11 @@ Arch::Arch(ArchArgs args) : args(args)
|
|||||||
default_tags.resize(max_tag_count);
|
default_tags.resize(max_tag_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Arch::init() {
|
||||||
|
dedicated_interconnect.init(getCtx());
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
std::string Arch::getChipName() const { return chip_info->name.get(); }
|
std::string Arch::getChipName() const { return chip_info->name.get(); }
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "constraints.h"
|
#include "constraints.h"
|
||||||
|
#include "dedicated_interconnect.h"
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
@ -772,6 +773,8 @@ struct ArchRanges
|
|||||||
using BucketBelRangeT = FilteredBelRange;
|
using BucketBelRangeT = FilteredBelRange;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DedicatedInterconnect;
|
||||||
|
|
||||||
struct Arch : ArchAPI<ArchRanges>
|
struct Arch : ArchAPI<ArchRanges>
|
||||||
{
|
{
|
||||||
boost::iostreams::mapped_file_source blob_file;
|
boost::iostreams::mapped_file_source blob_file;
|
||||||
@ -783,8 +786,6 @@ struct Arch : ArchAPI<ArchRanges>
|
|||||||
|
|
||||||
std::unordered_map<WireId, NetInfo *> wire_to_net;
|
std::unordered_map<WireId, NetInfo *> wire_to_net;
|
||||||
std::unordered_map<PipId, NetInfo *> pip_to_net;
|
std::unordered_map<PipId, NetInfo *> pip_to_net;
|
||||||
std::unordered_map<WireId, std::pair<int, int>> driving_pip_loc;
|
|
||||||
std::unordered_map<WireId, NetInfo *> reserved_wires;
|
|
||||||
|
|
||||||
static constexpr size_t kMaxState = 8;
|
static constexpr size_t kMaxState = 8;
|
||||||
|
|
||||||
@ -811,10 +812,12 @@ struct Arch : ArchAPI<ArchRanges>
|
|||||||
std::vector<SiteRouter> sites;
|
std::vector<SiteRouter> sites;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DedicatedInterconnect dedicated_interconnect;
|
||||||
std::unordered_map<int32_t, TileStatus> tileStatus;
|
std::unordered_map<int32_t, TileStatus> tileStatus;
|
||||||
|
|
||||||
ArchArgs args;
|
ArchArgs args;
|
||||||
Arch(ArchArgs args);
|
Arch(ArchArgs args);
|
||||||
|
void init();
|
||||||
|
|
||||||
std::string getChipName() const override;
|
std::string getChipName() const override;
|
||||||
|
|
||||||
@ -1236,9 +1239,6 @@ struct Arch : ArchAPI<ArchRanges>
|
|||||||
NPNR_ASSERT(wire_to_net[dst] == nullptr || wire_to_net[dst] == net);
|
NPNR_ASSERT(wire_to_net[dst] == nullptr || wire_to_net[dst] == net);
|
||||||
|
|
||||||
pip_to_net[pip] = net;
|
pip_to_net[pip] = net;
|
||||||
std::pair<int, int> loc;
|
|
||||||
get_tile_x_y(pip.tile, &loc.first, &loc.second);
|
|
||||||
driving_pip_loc[dst] = loc;
|
|
||||||
|
|
||||||
wire_to_net[dst] = net;
|
wire_to_net[dst] = net;
|
||||||
net->wires[dst].pip = pip;
|
net->wires[dst].pip = pip;
|
||||||
@ -1509,6 +1509,10 @@ struct Arch : ArchAPI<ArchRanges>
|
|||||||
if (cell == nullptr) {
|
if (cell == nullptr) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
if(!dedicated_interconnect.isBelLocationValid(bel, cell)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (io_port_types.count(cell->type)) {
|
if (io_port_types.count(cell->type)) {
|
||||||
// FIXME: Probably need to actually constraint io port cell/bel,
|
// FIXME: Probably need to actually constraint io port cell/bel,
|
||||||
// but the current BBA emission doesn't support that. This only
|
// but the current BBA emission doesn't support that. This only
|
||||||
|
351
fpga_interchange/dedicated_interconnect.cc
Normal file
351
fpga_interchange/dedicated_interconnect.cc
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
/*
|
||||||
|
* 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 "nextpnr.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
void DedicatedInterconnect::init(const Context *ctx) {
|
||||||
|
this->ctx = ctx;
|
||||||
|
|
||||||
|
if(ctx->debug) {
|
||||||
|
log_info("Finding dedicated interconnect!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
find_dedicated_interconnect();
|
||||||
|
if(ctx->debug) {
|
||||||
|
print_dedicated_interconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DedicatedInterconnect::check_routing(
|
||||||
|
BelId src_bel, IdString src_bel_pin,
|
||||||
|
BelId dst_bel, IdString dst_bel_pin) const {
|
||||||
|
// FIXME: Implement.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DedicatedInterconnect::isBelLocationValid(BelId bel, const CellInfo* cell) const {
|
||||||
|
NPNR_ASSERT(bel != BelId());
|
||||||
|
|
||||||
|
Loc bel_loc = ctx->getBelLocation(bel);
|
||||||
|
|
||||||
|
const auto &bel_data = bel_info(ctx->chip_info, bel);
|
||||||
|
|
||||||
|
for(const auto &port_pair : cell->ports) {
|
||||||
|
IdString port_name = port_pair.first;
|
||||||
|
NetInfo *net = port_pair.second.net;
|
||||||
|
if(net == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only check sink BELs.
|
||||||
|
if(net->driver.cell == cell && net->driver.port == port_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This net doesn't have a driver, probably not valid?
|
||||||
|
NPNR_ASSERT(net->driver.cell != nullptr);
|
||||||
|
|
||||||
|
BelId driver_bel = net->driver.cell->bel;
|
||||||
|
if(driver_bel == BelId()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &driver_bel_data = bel_info(ctx->chip_info, driver_bel);
|
||||||
|
|
||||||
|
Loc driver_loc = ctx->getBelLocation(driver_bel);
|
||||||
|
|
||||||
|
DeltaTileTypeBelPin driver_type_bel_pin;
|
||||||
|
driver_type_bel_pin.delta_x = driver_loc.x - bel_loc.x;
|
||||||
|
driver_type_bel_pin.delta_y = driver_loc.y - bel_loc.y;
|
||||||
|
driver_type_bel_pin.type_bel_pin.tile_type = ctx->chip_info->tiles[driver_bel.tile].type;
|
||||||
|
driver_type_bel_pin.type_bel_pin.bel_index = driver_bel.index;
|
||||||
|
driver_type_bel_pin.type_bel_pin.bel_pin = get_only_value(ctx->getBelPinsForCellPin(net->driver.cell, net->driver.port));
|
||||||
|
|
||||||
|
for(IdString bel_pin : ctx->getBelPinsForCellPin(cell, port_name)) {
|
||||||
|
TileTypeBelPin type_bel_pin;
|
||||||
|
type_bel_pin.tile_type = ctx->chip_info->tiles[bel.tile].type;
|
||||||
|
type_bel_pin.bel_index = bel.index;
|
||||||
|
type_bel_pin.bel_pin = bel_pin;
|
||||||
|
|
||||||
|
auto iter = pins_with_dedicate_interconnect.find(type_bel_pin);
|
||||||
|
if(iter == pins_with_dedicate_interconnect.end()) {
|
||||||
|
// This BEL pin doesn't have a dedicate interconnect.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bel.tile == driver_bel.tile && bel_data.site == driver_bel_data.site) {
|
||||||
|
// This is a site local routing, even though this is a sink
|
||||||
|
// with a dedicated interconnect.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do fast routing check to see if the pair of driver and sink
|
||||||
|
// every are valid.
|
||||||
|
if(iter->second.count(driver_type_bel_pin) == 0) {
|
||||||
|
if(ctx->verbose) {
|
||||||
|
log_info("BEL %s is not valid because pin %s cannot be driven by %s/%s\n",
|
||||||
|
ctx->nameOfBel(bel),
|
||||||
|
bel_pin.c_str(ctx),
|
||||||
|
ctx->nameOfBel(driver_bel),
|
||||||
|
driver_type_bel_pin.type_bel_pin.bel_pin.c_str(ctx));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do detailed routing check to ensure driver can reach sink.
|
||||||
|
//
|
||||||
|
// FIXME: This might be too slow, but it handles a case on
|
||||||
|
// SLICEL.COUT -> SLICEL.CIN has delta_y = {1, 2}, but the
|
||||||
|
// delta_y=2 case is rare.
|
||||||
|
if(!check_routing(
|
||||||
|
driver_bel, driver_type_bel_pin.type_bel_pin.bel_pin,
|
||||||
|
bel, bel_pin)) {
|
||||||
|
if(ctx->verbose) {
|
||||||
|
log_info("BEL %s is not valid because pin %s cannot be driven by %s/%s (via detailed check)\n",
|
||||||
|
ctx->nameOfBel(bel),
|
||||||
|
bel_pin.c_str(ctx),
|
||||||
|
ctx->nameOfBel(driver_bel),
|
||||||
|
driver_type_bel_pin.type_bel_pin.bel_pin.c_str(ctx));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DedicatedInterconnect::print_dedicated_interconnect() const {
|
||||||
|
log_info("Found %zu sinks with dedicated interconnect\n", pins_with_dedicate_interconnect.size());
|
||||||
|
std::vector<TileTypeBelPin> sorted_keys;
|
||||||
|
for(const auto & sink_to_srcs : pins_with_dedicate_interconnect) {
|
||||||
|
sorted_keys.push_back(sink_to_srcs.first);
|
||||||
|
}
|
||||||
|
std::sort(sorted_keys.begin(), sorted_keys.end());
|
||||||
|
|
||||||
|
for(const auto & dst : sorted_keys) {
|
||||||
|
for(const auto & src : pins_with_dedicate_interconnect.at(dst)) {
|
||||||
|
const TileTypeInfoPOD & src_tile_type = ctx->chip_info->tile_types[src.type_bel_pin.tile_type];
|
||||||
|
const BelInfoPOD & src_bel_info = src_tile_type.bel_data[src.type_bel_pin.bel_index];
|
||||||
|
IdString src_site_type = IdString(src_tile_type.site_types[src_bel_info.site]);
|
||||||
|
IdString src_bel_pin = src.type_bel_pin.bel_pin;
|
||||||
|
|
||||||
|
const TileTypeInfoPOD & dst_tile_type = ctx->chip_info->tile_types[dst.tile_type];
|
||||||
|
const BelInfoPOD & dst_bel_info = dst_tile_type.bel_data[dst.bel_index];
|
||||||
|
IdString dst_site_type = IdString(dst_tile_type.site_types[dst_bel_info.site]);
|
||||||
|
IdString dst_bel_pin = dst.bel_pin;
|
||||||
|
|
||||||
|
log_info("%s.%s/%s/%s (%d, %d) -> %s.%s/%s/%s\n",
|
||||||
|
IdString(src_tile_type.name).c_str(ctx),
|
||||||
|
src_site_type.c_str(ctx),
|
||||||
|
IdString(src_bel_info.name).c_str(ctx),
|
||||||
|
src_bel_pin.c_str(ctx),
|
||||||
|
src.delta_x,
|
||||||
|
src.delta_y,
|
||||||
|
IdString(dst_tile_type.name).c_str(ctx),
|
||||||
|
dst_site_type.c_str(ctx),
|
||||||
|
IdString(dst_bel_info.name).c_str(ctx),
|
||||||
|
dst_bel_pin.c_str(ctx));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DedicatedInterconnect::find_dedicated_interconnect() {
|
||||||
|
for(BelId bel : ctx->getBels()) {
|
||||||
|
const auto & bel_data = bel_info(ctx->chip_info, bel);
|
||||||
|
if(bel_data.category != BEL_CATEGORY_LOGIC) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(bel_data.synthetic) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t i = 0; i < bel_data.num_bel_wires; ++i) {
|
||||||
|
if(bel_data.types[i] != PORT_IN) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
WireId wire;
|
||||||
|
wire.tile = bel.tile;
|
||||||
|
wire.index = bel_data.wires[i];
|
||||||
|
|
||||||
|
expand_bel(bel, IdString(bel_data.ports[i]), wire);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All legal routes involved at most 2 sites, the source site and the sink
|
||||||
|
// site. The source site and sink sites may be the same, but that is not
|
||||||
|
// dedicated routing, that is intra site routing.
|
||||||
|
//
|
||||||
|
// Dedicated routing must leave the sink site, traverse some routing and
|
||||||
|
// terminate at another site. Routing that "flys" over a site is expressed as
|
||||||
|
// a psuedo-pip connected the relevant site pin wires, rather than traversing
|
||||||
|
// the site.
|
||||||
|
enum WireNodeState {
|
||||||
|
IN_SINK_SITE = 0,
|
||||||
|
IN_ROUTING = 1,
|
||||||
|
IN_SOURCE_SITE = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WireNode {
|
||||||
|
WireId wire;
|
||||||
|
WireNodeState state;
|
||||||
|
int depth;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Maximum depth that a dedicate interconnect is considered.
|
||||||
|
//
|
||||||
|
// Routing networks with depth <= kMaxDepth is considers a dedicated
|
||||||
|
// interconnect.
|
||||||
|
constexpr int kMaxDepth = 20;
|
||||||
|
|
||||||
|
void DedicatedInterconnect::expand_bel(BelId bel, IdString pin, WireId wire) {
|
||||||
|
NPNR_ASSERT(bel != BelId());
|
||||||
|
|
||||||
|
std::vector<WireNode> nodes_to_expand;
|
||||||
|
|
||||||
|
const auto & src_wire_data = ctx->wire_info(wire);
|
||||||
|
NPNR_ASSERT(src_wire_data.site != -1);
|
||||||
|
|
||||||
|
WireNode wire_node;
|
||||||
|
wire_node.wire = wire;
|
||||||
|
wire_node.state = IN_SINK_SITE;
|
||||||
|
wire_node.depth = 0;
|
||||||
|
|
||||||
|
nodes_to_expand.push_back(wire_node);
|
||||||
|
|
||||||
|
Loc sink_loc = ctx->getBelLocation(bel);
|
||||||
|
std::unordered_set<DeltaTileTypeBelPin> srcs;
|
||||||
|
|
||||||
|
while(!nodes_to_expand.empty()) {
|
||||||
|
WireNode node_to_expand = nodes_to_expand.back();
|
||||||
|
nodes_to_expand.pop_back();
|
||||||
|
|
||||||
|
for(PipId pip : ctx->getPipsUphill(node_to_expand.wire)) {
|
||||||
|
if(ctx->is_pip_synthetic(pip)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
WireId wire = ctx->getPipSrcWire(pip);
|
||||||
|
if(wire == WireId()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
WireNode next_node;
|
||||||
|
next_node.wire = wire;
|
||||||
|
next_node.depth = node_to_expand.depth += 1;
|
||||||
|
|
||||||
|
if(next_node.depth > kMaxDepth) {
|
||||||
|
// Dedicated routing should reach sources by kMaxDepth (with
|
||||||
|
// tuning).
|
||||||
|
//
|
||||||
|
// FIXME: Consider removing kMaxDepth and use kMaxSources?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const & wire_data = ctx->wire_info(wire);
|
||||||
|
|
||||||
|
bool expand_node = true;
|
||||||
|
if(ctx->is_site_port(pip)) {
|
||||||
|
switch(node_to_expand.state) {
|
||||||
|
case IN_SINK_SITE:
|
||||||
|
NPNR_ASSERT(wire_data.site == -1);
|
||||||
|
next_node.state = IN_ROUTING;
|
||||||
|
break;
|
||||||
|
case IN_ROUTING:
|
||||||
|
NPNR_ASSERT(wire_data.site != -1);
|
||||||
|
if(wire_data.site == src_wire_data.site) {
|
||||||
|
// Dedicated routing won't have straight loops,
|
||||||
|
// general routing looks like that.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next_node.state = IN_SOURCE_SITE;
|
||||||
|
break;
|
||||||
|
case IN_SOURCE_SITE:
|
||||||
|
// Once entering a site, do not leave it again.
|
||||||
|
// This path is not a legal route!
|
||||||
|
expand_node = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Unreachable!!!
|
||||||
|
NPNR_ASSERT(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next_node.state = node_to_expand.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(expand_node) {
|
||||||
|
nodes_to_expand.push_back(next_node);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(next_node.state == IN_SOURCE_SITE) {
|
||||||
|
for(BelPin bel_pin : ctx->getWireBelPins(wire)) {
|
||||||
|
BelId src_bel = bel_pin.bel;
|
||||||
|
auto const & bel_data = bel_info(ctx->chip_info, src_bel);
|
||||||
|
NPNR_ASSERT(bel_data.site != src_wire_data.site);
|
||||||
|
|
||||||
|
if(bel_data.category != BEL_CATEGORY_LOGIC) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(bel_data.synthetic) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(ctx->getBelPinType(bel_pin.bel, bel_pin.pin) != PORT_OUT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Loc src_loc = ctx->getBelLocation(src_bel);
|
||||||
|
|
||||||
|
DeltaTileTypeBelPin delta_type_bel_pin;
|
||||||
|
delta_type_bel_pin.delta_x = src_loc.x - sink_loc.x;
|
||||||
|
delta_type_bel_pin.delta_x = src_loc.y - sink_loc.y;
|
||||||
|
delta_type_bel_pin.type_bel_pin.tile_type = ctx->chip_info->tiles[src_bel.tile].type;
|
||||||
|
delta_type_bel_pin.type_bel_pin.bel_index = src_bel.index;
|
||||||
|
delta_type_bel_pin.type_bel_pin.bel_pin = bel_pin.pin;
|
||||||
|
srcs.emplace(delta_type_bel_pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TileTypeBelPin type_bel_pin;
|
||||||
|
type_bel_pin.tile_type = ctx->chip_info->tiles[bel.tile].type;
|
||||||
|
type_bel_pin.bel_index = bel.index;
|
||||||
|
type_bel_pin.bel_pin = pin;
|
||||||
|
|
||||||
|
auto result = pins_with_dedicate_interconnect.emplace(type_bel_pin, srcs);
|
||||||
|
if(!result.second) {
|
||||||
|
// type_bel_pin was already present! Add any new sources from this
|
||||||
|
// sink type (if any);
|
||||||
|
for(auto src : srcs) {
|
||||||
|
result.first->second.emplace(src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_END
|
129
fpga_interchange/dedicated_interconnect.h
Normal file
129
fpga_interchange/dedicated_interconnect.h
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NEXTPNR_H
|
||||||
|
#error Include "dedicated_interconnect.h" via "nextpnr.h" only.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
struct TileTypeBelPin {
|
||||||
|
int32_t tile_type;
|
||||||
|
int32_t bel_index;
|
||||||
|
IdString bel_pin;
|
||||||
|
|
||||||
|
bool operator < (const TileTypeBelPin &other) const {
|
||||||
|
if(tile_type >= other.tile_type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bel_index >= other.bel_index) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bel_pin < other.bel_pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator ==(const TileTypeBelPin &other) const {
|
||||||
|
return tile_type == other.tile_type && bel_index == other.bel_index && bel_pin == other.bel_pin;
|
||||||
|
}
|
||||||
|
bool operator !=(const TileTypeBelPin &other) const {
|
||||||
|
return tile_type != other.tile_type || bel_index != other.bel_index || bel_pin != other.bel_pin;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeltaTileTypeBelPin {
|
||||||
|
int32_t delta_x;
|
||||||
|
int32_t delta_y;
|
||||||
|
TileTypeBelPin type_bel_pin;
|
||||||
|
|
||||||
|
bool operator ==(const DeltaTileTypeBelPin &other) const {
|
||||||
|
return delta_x == other.delta_x && delta_y == other.delta_y && type_bel_pin == other.type_bel_pin;
|
||||||
|
}
|
||||||
|
bool operator !=(const DeltaTileTypeBelPin &other) const {
|
||||||
|
return delta_x != other.delta_x || delta_y != other.delta_y || type_bel_pin != other.type_bel_pin;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
|
template <> struct std::hash<NEXTPNR_NAMESPACE_PREFIX TileTypeBelPin>
|
||||||
|
{
|
||||||
|
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX TileTypeBelPin &type_bel_pin) const noexcept
|
||||||
|
{
|
||||||
|
std::size_t seed = 0;
|
||||||
|
boost::hash_combine(seed, std::hash<int32_t>()(type_bel_pin.tile_type));
|
||||||
|
boost::hash_combine(seed, std::hash<int32_t>()(type_bel_pin.bel_index));
|
||||||
|
boost::hash_combine(seed, std::hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(type_bel_pin.bel_pin));
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct std::hash<NEXTPNR_NAMESPACE_PREFIX DeltaTileTypeBelPin>
|
||||||
|
{
|
||||||
|
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX DeltaTileTypeBelPin &delta_bel_pin) const noexcept
|
||||||
|
{
|
||||||
|
std::size_t seed = 0;
|
||||||
|
boost::hash_combine(seed, std::hash<int32_t>()(delta_bel_pin.delta_x));
|
||||||
|
boost::hash_combine(seed, std::hash<int32_t>()(delta_bel_pin.delta_y));
|
||||||
|
boost::hash_combine(seed, std::hash<NEXTPNR_NAMESPACE_PREFIX TileTypeBelPin>()(delta_bel_pin.type_bel_pin));
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
struct Context;
|
||||||
|
|
||||||
|
// This class models dedicated interconnect present in the given fabric.
|
||||||
|
//
|
||||||
|
// Examples of dedicate interconnect:
|
||||||
|
// - IBUF.O -> ISERDES.I
|
||||||
|
// - IBUF.O -> IDELAY.I
|
||||||
|
// - CARRY4.CO[3] -> CARRY4.CIN
|
||||||
|
//
|
||||||
|
// Note that CARRY4.CYINIT does not **require** dedicated interconnect, so
|
||||||
|
// it doesn't qualify.
|
||||||
|
//
|
||||||
|
// This class discovers dedicated interconnect by examing the routing graph.
|
||||||
|
// This discovery make be expensive, and require caching to accelerate
|
||||||
|
// startup.
|
||||||
|
struct DedicatedInterconnect {
|
||||||
|
const Context *ctx;
|
||||||
|
|
||||||
|
std::unordered_map<TileTypeBelPin, std::unordered_set<DeltaTileTypeBelPin>> pins_with_dedicate_interconnect;
|
||||||
|
|
||||||
|
void init(const Context *ctx);
|
||||||
|
|
||||||
|
// Is this BEL placed in a location that is valid based on dedicated
|
||||||
|
// interconnect?
|
||||||
|
//
|
||||||
|
// Note: Only BEL pin sinks are checked.
|
||||||
|
bool isBelLocationValid(BelId bel, const CellInfo* cell) const;
|
||||||
|
|
||||||
|
void find_dedicated_interconnect();
|
||||||
|
void print_dedicated_interconnect() const;
|
||||||
|
bool check_routing(
|
||||||
|
BelId src_bel, IdString src_bel_pin,
|
||||||
|
BelId dst_bel, IdString dst_bel_pin) const;
|
||||||
|
void expand_bel(BelId bel, IdString pin, WireId wire);
|
||||||
|
};
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_END
|
8
fpga_interchange/examples/ff/Makefile
Normal file
8
fpga_interchange/examples/ff/Makefile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
DESIGN := ff
|
||||||
|
DESIGN_TOP := top
|
||||||
|
PACKAGE := csg324
|
||||||
|
|
||||||
|
include ../template.mk
|
||||||
|
|
||||||
|
build/ff.json: ff.v | build
|
||||||
|
yosys -c run.tcl
|
11
fpga_interchange/examples/ff/ff.v
Normal file
11
fpga_interchange/examples/ff/ff.v
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
module top(input clk, input d, input r, output reg q);
|
||||||
|
|
||||||
|
always @(posedge clk)
|
||||||
|
begin
|
||||||
|
if(r)
|
||||||
|
q <= 1'b0;
|
||||||
|
else
|
||||||
|
q <= d;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
9
fpga_interchange/examples/ff/ff.xdc
Normal file
9
fpga_interchange/examples/ff/ff.xdc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
set_property PACKAGE_PIN P17 [get_ports clk]
|
||||||
|
set_property PACKAGE_PIN N15 [get_ports d]
|
||||||
|
set_property PACKAGE_PIN N16 [get_ports r]
|
||||||
|
set_property PACKAGE_PIN M17 [get_ports q]
|
||||||
|
|
||||||
|
set_property IOSTANDARD LVCMOS33 [get_ports clk]
|
||||||
|
set_property IOSTANDARD LVCMOS33 [get_ports d]
|
||||||
|
set_property IOSTANDARD LVCMOS33 [get_ports r]
|
||||||
|
set_property IOSTANDARD LVCMOS33 [get_ports q]
|
14
fpga_interchange/examples/ff/run.tcl
Normal file
14
fpga_interchange/examples/ff/run.tcl
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
yosys -import
|
||||||
|
|
||||||
|
read_verilog ff.v
|
||||||
|
|
||||||
|
synth_xilinx -nolutram -nowidelut -nosrl -nocarry -nodsp
|
||||||
|
|
||||||
|
# opt_expr -undriven makes sure all nets are driven, if only by the $undef
|
||||||
|
# net.
|
||||||
|
opt_expr -undriven
|
||||||
|
opt_clean
|
||||||
|
|
||||||
|
setundef -zero -params
|
||||||
|
|
||||||
|
write_json build/ff.json
|
@ -311,9 +311,6 @@ void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::str
|
|||||||
for(auto & cell_name : placed_cells) {
|
for(auto & cell_name : placed_cells) {
|
||||||
const CellInfo & cell = *ctx->cells.at(cell_name);
|
const CellInfo & cell = *ctx->cells.at(cell_name);
|
||||||
|
|
||||||
if(ctx->io_port_types.count(cell.type)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(ctx->is_bel_synthetic(cell.bel)) {
|
if(ctx->is_bel_synthetic(cell.bel)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -321,15 +318,14 @@ void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::str
|
|||||||
number_placements += 1;
|
number_placements += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<IdString> ports;
|
||||||
|
|
||||||
std::unordered_map<std::string, std::string> sites;
|
std::unordered_map<std::string, std::string> sites;
|
||||||
auto placements = phys_netlist.initPlacements(number_placements);
|
auto placements = phys_netlist.initPlacements(number_placements);
|
||||||
auto placement_iter = placements.begin();
|
auto placement_iter = placements.begin();
|
||||||
|
|
||||||
for(auto & cell_name : placed_cells) {
|
for(auto & cell_name : placed_cells) {
|
||||||
const CellInfo & cell = *ctx->cells.at(cell_name);
|
const CellInfo & cell = *ctx->cells.at(cell_name);
|
||||||
if(ctx->io_port_types.count(cell.type)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(ctx->is_bel_synthetic(cell.bel)) {
|
if(ctx->is_bel_synthetic(cell.bel)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -351,7 +347,13 @@ void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::str
|
|||||||
auto placement = *placement_iter++;
|
auto placement = *placement_iter++;
|
||||||
|
|
||||||
placement.setCellName(strings.get_index(cell.name.str(ctx)));
|
placement.setCellName(strings.get_index(cell.name.str(ctx)));
|
||||||
placement.setType(strings.get_index(cell.type.str(ctx)));
|
if(ctx->io_port_types.count(cell.type)) {
|
||||||
|
// Always mark IO ports as type <PORT>.
|
||||||
|
placement.setType(strings.get_index("<PORT>"));
|
||||||
|
ports.push_back(cell.name);
|
||||||
|
} else {
|
||||||
|
placement.setType(strings.get_index(cell.type.str(ctx)));
|
||||||
|
}
|
||||||
placement.setSite(strings.get_index(site_name));
|
placement.setSite(strings.get_index(site_name));
|
||||||
|
|
||||||
size_t bel_index = strings.get_index(bel_name[1].str(ctx));
|
size_t bel_index = strings.get_index(bel_name[1].str(ctx));
|
||||||
@ -359,27 +361,38 @@ void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::str
|
|||||||
placement.setIsBelFixed(cell.belStrength >= STRENGTH_FIXED);
|
placement.setIsBelFixed(cell.belStrength >= STRENGTH_FIXED);
|
||||||
placement.setIsSiteFixed(cell.belStrength >= STRENGTH_FIXED);
|
placement.setIsSiteFixed(cell.belStrength >= STRENGTH_FIXED);
|
||||||
|
|
||||||
size_t pin_count = 0;
|
if(!ctx->io_port_types.count(cell.type)) {
|
||||||
for(const auto & pin : cell.cell_bel_pins) {
|
// Don't emit pin map for ports.
|
||||||
pin_count += pin.second.size();
|
size_t pin_count = 0;
|
||||||
}
|
for(const auto & pin : cell.cell_bel_pins) {
|
||||||
|
pin_count += pin.second.size();
|
||||||
|
}
|
||||||
|
|
||||||
auto pins = placement.initPinMap(pin_count);
|
auto pins = placement.initPinMap(pin_count);
|
||||||
auto pin_iter = pins.begin();
|
auto pin_iter = pins.begin();
|
||||||
|
|
||||||
for(const auto & cell_to_bel_pins : cell.cell_bel_pins) {
|
for(const auto & cell_to_bel_pins : cell.cell_bel_pins) {
|
||||||
std::string cell_pin = cell_to_bel_pins.first.str(ctx);
|
std::string cell_pin = cell_to_bel_pins.first.str(ctx);
|
||||||
size_t cell_pin_index = strings.get_index(cell_pin);
|
size_t cell_pin_index = strings.get_index(cell_pin);
|
||||||
|
|
||||||
for(const auto & bel_pin : cell_to_bel_pins.second) {
|
for(const auto & bel_pin : cell_to_bel_pins.second) {
|
||||||
auto pin_output = *pin_iter++;
|
auto pin_output = *pin_iter++;
|
||||||
pin_output.setCellPin(cell_pin_index);
|
pin_output.setCellPin(cell_pin_index);
|
||||||
pin_output.setBel(bel_index);
|
pin_output.setBel(bel_index);
|
||||||
pin_output.setBelPin(strings.get_index(bel_pin.str(ctx)));
|
pin_output.setBelPin(strings.get_index(bel_pin.str(ctx)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto phys_cells = phys_netlist.initPhysCells(ports.size());
|
||||||
|
auto phys_cells_iter = phys_cells.begin();
|
||||||
|
for(IdString port : ports) {
|
||||||
|
auto phys_cell = *phys_cells_iter++;
|
||||||
|
phys_cell.setCellName(strings.get_index(port.str(ctx)));
|
||||||
|
phys_cell.setPhysType(PhysicalNetlist::PhysNetlist::PhysCellType::PORT);
|
||||||
|
}
|
||||||
|
|
||||||
auto nets = phys_netlist.initPhysNets(ctx->nets.size());
|
auto nets = phys_netlist.initPhysNets(ctx->nets.size());
|
||||||
auto net_iter = nets.begin();
|
auto net_iter = nets.begin();
|
||||||
for(auto & net_pair : ctx->nets) {
|
for(auto & net_pair : ctx->nets) {
|
||||||
|
@ -70,6 +70,7 @@ void FpgaInterchangeCommandHandler::customBitstream(Context *ctx)
|
|||||||
std::unique_ptr<Context> FpgaInterchangeCommandHandler::createContext(std::unordered_map<std::string, Property> &values)
|
std::unique_ptr<Context> FpgaInterchangeCommandHandler::createContext(std::unordered_map<std::string, Property> &values)
|
||||||
{
|
{
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
ArchArgs chipArgs;
|
ArchArgs chipArgs;
|
||||||
if (!vm.count("chipdb")) {
|
if (!vm.count("chipdb")) {
|
||||||
log_error("chip database binary must be provided\n");
|
log_error("chip database binary must be provided\n");
|
||||||
@ -81,6 +82,16 @@ std::unique_ptr<Context> FpgaInterchangeCommandHandler::createContext(std::unord
|
|||||||
|
|
||||||
auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
|
auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
|
||||||
|
|
||||||
|
if (vm.count("verbose")) {
|
||||||
|
ctx->verbose = true;
|
||||||
|
}
|
||||||
|
if (vm.count("debug")) {
|
||||||
|
ctx->verbose = true;
|
||||||
|
ctx->debug = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->init();
|
||||||
|
|
||||||
if (vm.count("netlist")) {
|
if (vm.count("netlist")) {
|
||||||
ctx->read_logical_netlist(vm["netlist"].as<std::string>());
|
ctx->read_logical_netlist(vm["netlist"].as<std::string>());
|
||||||
}
|
}
|
||||||
|
@ -260,6 +260,12 @@ struct SiteInformation
|
|||||||
if (!result.second && result.first->second != net) {
|
if (!result.second && result.first->second != net) {
|
||||||
// Conflict, this wire is already in use and it's not
|
// Conflict, this wire is already in use and it's not
|
||||||
// doesn't match!
|
// doesn't match!
|
||||||
|
if(verbose_site_router(ctx)) {
|
||||||
|
log_info("Cannot select route because net %s != net %s\n",
|
||||||
|
result.first->second->name.c_str(ctx),
|
||||||
|
net->name.c_str(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user