Merge pull request #591 from litghost/add_constant_network
Add constant network support to FPGA interchange arch
This commit is contained in:
commit
ab8dfcfba4
@ -69,7 +69,7 @@ template <size_t StateCount, typename StateType = int8_t, typename CountType = u
|
||||
|
||||
bool add_implies(int32_t next_state)
|
||||
{
|
||||
NPNR_ASSERT(next_state < StateCount);
|
||||
NPNR_ASSERT(next_state >= 0 && (size_t)next_state < StateCount);
|
||||
|
||||
// Increment and mark the state as selected.
|
||||
count[next_state] += 1;
|
||||
@ -92,7 +92,7 @@ template <size_t StateCount, typename StateType = int8_t, typename CountType = u
|
||||
|
||||
void remove_implies(int32_t next_state)
|
||||
{
|
||||
NPNR_ASSERT(next_state < StateCount);
|
||||
NPNR_ASSERT(next_state >= 0 && (size_t)next_state < StateCount);
|
||||
NPNR_ASSERT(selected_states[next_state]);
|
||||
|
||||
count[next_state] -= 1;
|
||||
|
@ -40,14 +40,14 @@ void ExclusiveStateGroup<StateCount, StateType, CountType>::print_debug(const Co
|
||||
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]);
|
||||
definition.states.at(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]);
|
||||
log_info(" - %s, count = %d\n", definition.states.at(i).c_str(ctx), count[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,9 +62,9 @@ void ExclusiveStateGroup<StateCount, StateType, CountType>::explain_implies(cons
|
||||
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));
|
||||
log_info("Placing cell %s at bel %s does violates %s.%s, desired state = %s.\n", cell.c_str(ctx),
|
||||
ctx->nameOfBel(bel), object.c_str(ctx), definition.prefix.c_str(ctx),
|
||||
definition.states.at(next_state).c_str(ctx));
|
||||
print_debug(ctx, object, definition);
|
||||
}
|
||||
}
|
||||
@ -83,11 +83,10 @@ void ExclusiveStateGroup<StateCount, StateType, CountType>::explain_requires(con
|
||||
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));
|
||||
definition.states.at(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));
|
||||
log_info(" - %s\n", definition.states.at(required_state).c_str(ctx));
|
||||
}
|
||||
print_debug(ctx, object, definition);
|
||||
}
|
||||
|
@ -195,6 +195,8 @@ Arch::Arch(ArchArgs args) : args(args)
|
||||
default_tags.resize(max_tag_count);
|
||||
}
|
||||
|
||||
void Arch::init() { dedicated_interconnect.init(getCtx()); }
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
std::string Arch::getChipName() const { return chip_info->name.get(); }
|
||||
@ -217,7 +219,7 @@ void Arch::setup_byname() const
|
||||
for (int i = 0; i < chip_info->tiles.ssize(); i++) {
|
||||
auto &tile = chip_info->tiles[i];
|
||||
auto &tile_type = chip_info->tile_types[tile.type];
|
||||
for (int j = 0; j < tile_type.number_sites; j++) {
|
||||
for (size_t j = 0; j < tile_type.site_types.size(); j++) {
|
||||
auto &site = chip_info->sites[tile.sites[j]];
|
||||
site_by_name[id(site.name.get())] = std::make_pair(i, j);
|
||||
}
|
||||
@ -601,6 +603,7 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay
|
||||
|
||||
bool Arch::pack()
|
||||
{
|
||||
merge_constant_nets();
|
||||
pack_ports();
|
||||
return true;
|
||||
}
|
||||
@ -609,6 +612,14 @@ bool Arch::place()
|
||||
{
|
||||
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
|
||||
|
||||
// Re-map BEL pins without constant pins
|
||||
for (BelId bel : getBels()) {
|
||||
CellInfo *cell = getBoundBelCell(bel);
|
||||
if (cell != nullptr && cell->cell_mapping != -1) {
|
||||
map_cell_pins(cell, cell->cell_mapping, /*bind_constants=*/false);
|
||||
}
|
||||
}
|
||||
|
||||
if (placer == "heap") {
|
||||
PlacerHeapCfg cfg(getCtx());
|
||||
cfg.criticalityExponent = 7;
|
||||
@ -638,6 +649,14 @@ bool Arch::route()
|
||||
{
|
||||
std::string router = str_or_default(settings, id("router"), defaultRouter);
|
||||
|
||||
// Re-map BEL pins with constant pins
|
||||
for (BelId bel : getBels()) {
|
||||
CellInfo *cell = getBoundBelCell(bel);
|
||||
if (cell != nullptr && cell->cell_mapping != -1) {
|
||||
map_cell_pins(cell, cell->cell_mapping, /*bind_constants=*/true);
|
||||
}
|
||||
}
|
||||
|
||||
bool result;
|
||||
if (router == "router1") {
|
||||
result = router1(getCtx(), Router1Cfg(getCtx()));
|
||||
@ -677,13 +696,33 @@ DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; };
|
||||
delay_t Arch::estimateDelay(WireId src, WireId dst) const
|
||||
{
|
||||
// FIXME: Implement something to push the A* router in the right direction.
|
||||
return 0;
|
||||
int src_x, src_y;
|
||||
get_tile_x_y(src.tile, &src_x, &src_y);
|
||||
|
||||
int dst_x, dst_y;
|
||||
get_tile_x_y(dst.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;
|
||||
|
||||
base = (base * 3) / 2;
|
||||
return base;
|
||||
}
|
||||
|
||||
delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
|
||||
{
|
||||
// FIXME: Implement when adding timing-driven place and route.
|
||||
return 0;
|
||||
int src_x, src_y;
|
||||
get_tile_x_y(net_info->driver.cell->bel.tile, &src_x, &src_y);
|
||||
|
||||
int dst_x, dst_y;
|
||||
get_tile_x_y(sink.cell->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;
|
||||
|
||||
base = (base * 3) / 2;
|
||||
return base;
|
||||
}
|
||||
|
||||
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const
|
||||
@ -749,23 +788,64 @@ const std::vector<std::string> Arch::availablePlacers = {"sa",
|
||||
const std::string Arch::defaultRouter = "router2";
|
||||
const std::vector<std::string> Arch::availableRouters = {"router1", "router2"};
|
||||
|
||||
void Arch::map_cell_pins(CellInfo *cell, int32_t mapping) const
|
||||
void Arch::map_cell_pins(CellInfo *cell, int32_t mapping, bool bind_constants)
|
||||
{
|
||||
cell->cell_mapping = mapping;
|
||||
cell->cell_bel_pins.clear();
|
||||
for (IdString const_port : cell->const_ports) {
|
||||
NPNR_ASSERT(cell->ports.erase(const_port));
|
||||
}
|
||||
|
||||
const CellBelMapPOD &cell_pin_map = chip_info->cell_map->cell_bel_map[mapping];
|
||||
|
||||
IdString gnd_net_name(chip_info->constants->gnd_net_name);
|
||||
IdString vcc_net_name(chip_info->constants->vcc_net_name);
|
||||
|
||||
for (const auto &pin_map : cell_pin_map.common_pins) {
|
||||
IdString cell_pin(pin_map.cell_pin);
|
||||
IdString bel_pin(pin_map.bel_pin);
|
||||
|
||||
if (cell_pin.str(this) == "GND") {
|
||||
// FIXME: Tie this pin to the GND net
|
||||
if (bind_constants) {
|
||||
PortInfo port_info;
|
||||
port_info.name = bel_pin;
|
||||
port_info.type = PORT_IN;
|
||||
port_info.net = nullptr;
|
||||
|
||||
auto result = cell->ports.emplace(bel_pin, port_info);
|
||||
if (result.second) {
|
||||
cell->cell_bel_pins[bel_pin].push_back(bel_pin);
|
||||
connectPort(gnd_net_name, cell->name, bel_pin);
|
||||
cell->const_ports.emplace(bel_pin);
|
||||
} else {
|
||||
NPNR_ASSERT(result.first->second.net == getNetByAlias(gnd_net_name));
|
||||
auto result2 = cell->cell_bel_pins.emplace(bel_pin, std::vector<IdString>({bel_pin}));
|
||||
NPNR_ASSERT(result2.first->second.at(0) == bel_pin);
|
||||
NPNR_ASSERT(result2.first->second.size() == 1);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cell_pin.str(this) == "VCC") {
|
||||
// FIXME: Tie this pin to the VCC net
|
||||
if (bind_constants) {
|
||||
PortInfo port_info;
|
||||
port_info.name = bel_pin;
|
||||
port_info.type = PORT_IN;
|
||||
port_info.net = nullptr;
|
||||
|
||||
auto result = cell->ports.emplace(bel_pin, port_info);
|
||||
if (result.second) {
|
||||
cell->cell_bel_pins[bel_pin].push_back(bel_pin);
|
||||
connectPort(vcc_net_name, cell->name, bel_pin);
|
||||
cell->const_ports.emplace(bel_pin);
|
||||
} else {
|
||||
NPNR_ASSERT(result.first->second.net == getNetByAlias(vcc_net_name));
|
||||
auto result2 = cell->cell_bel_pins.emplace(bel_pin, std::vector<IdString>({bel_pin}));
|
||||
NPNR_ASSERT(result2.first->second.at(0) == bel_pin);
|
||||
NPNR_ASSERT(result2.first->second.size() == 1);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -790,11 +870,44 @@ void Arch::map_cell_pins(CellInfo *cell, int32_t mapping) const
|
||||
IdString bel_pin(pin_map.bel_pin);
|
||||
|
||||
if (cell_pin.str(this) == "GND") {
|
||||
// FIXME: Tie this pin to the GND net
|
||||
if (bind_constants) {
|
||||
PortInfo port_info;
|
||||
port_info.name = bel_pin;
|
||||
port_info.type = PORT_IN;
|
||||
|
||||
auto result = cell->ports.emplace(bel_pin, port_info);
|
||||
if (result.second) {
|
||||
cell->cell_bel_pins[bel_pin].push_back(bel_pin);
|
||||
connectPort(gnd_net_name, cell->name, bel_pin);
|
||||
cell->const_ports.emplace(bel_pin);
|
||||
} else {
|
||||
NPNR_ASSERT(result.first->second.net == getNetByAlias(gnd_net_name));
|
||||
auto result2 = cell->cell_bel_pins.emplace(bel_pin, std::vector<IdString>({bel_pin}));
|
||||
NPNR_ASSERT(result2.first->second.at(0) == bel_pin);
|
||||
NPNR_ASSERT(result2.first->second.size() == 1);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cell_pin.str(this) == "VCC") {
|
||||
// FIXME: Tie this pin to the VCC net
|
||||
if (bind_constants) {
|
||||
PortInfo port_info;
|
||||
port_info.name = bel_pin;
|
||||
port_info.type = PORT_IN;
|
||||
|
||||
auto result = cell->ports.emplace(bel_pin, port_info);
|
||||
if (result.second) {
|
||||
cell->cell_bel_pins[bel_pin].push_back(bel_pin);
|
||||
connectPort(vcc_net_name, cell->name, bel_pin);
|
||||
cell->const_ports.emplace(bel_pin);
|
||||
} else {
|
||||
NPNR_ASSERT(result.first->second.net == getNetByAlias(vcc_net_name));
|
||||
auto result2 = cell->cell_bel_pins.emplace(bel_pin, std::vector<IdString>({bel_pin}));
|
||||
NPNR_ASSERT(result2.first->second.at(0) == bel_pin);
|
||||
NPNR_ASSERT(result2.first->second.size() == 1);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -853,6 +966,175 @@ size_t Arch::get_cell_type_index(IdString cell_type) const
|
||||
return cell_offset;
|
||||
}
|
||||
|
||||
void Arch::merge_constant_nets()
|
||||
{
|
||||
NetInfo *gnd_net = nullptr;
|
||||
NetInfo *vcc_net = nullptr;
|
||||
|
||||
bool need_gnd_source = false;
|
||||
bool need_vcc_source = false;
|
||||
|
||||
IdString gnd_net_name(chip_info->constants->gnd_net_name);
|
||||
IdString gnd_cell_type(chip_info->constants->gnd_cell_name);
|
||||
IdString gnd_cell_port(chip_info->constants->gnd_cell_port);
|
||||
|
||||
auto gnd_iter = nets.find(gnd_net_name);
|
||||
if (gnd_iter != nets.end()) {
|
||||
NPNR_ASSERT(gnd_iter->second->driver.cell != nullptr);
|
||||
NPNR_ASSERT(gnd_iter->second->driver.cell->type == gnd_cell_type);
|
||||
NPNR_ASSERT(gnd_iter->second->driver.port == gnd_cell_port);
|
||||
|
||||
gnd_net = gnd_iter->second.get();
|
||||
} else {
|
||||
gnd_net = createNet(gnd_net_name);
|
||||
need_gnd_source = true;
|
||||
}
|
||||
|
||||
IdString vcc_net_name(chip_info->constants->vcc_net_name);
|
||||
IdString vcc_cell_type(chip_info->constants->vcc_cell_name);
|
||||
IdString vcc_cell_port(chip_info->constants->vcc_cell_port);
|
||||
|
||||
auto vcc_iter = nets.find(vcc_net_name);
|
||||
if (vcc_iter != nets.end()) {
|
||||
NPNR_ASSERT(vcc_iter->second->driver.cell != nullptr);
|
||||
NPNR_ASSERT(vcc_iter->second->driver.cell->type == vcc_cell_type);
|
||||
NPNR_ASSERT(vcc_iter->second->driver.port == vcc_cell_port);
|
||||
|
||||
vcc_net = vcc_iter->second.get();
|
||||
} else {
|
||||
vcc_net = createNet(vcc_net_name);
|
||||
need_vcc_source = true;
|
||||
}
|
||||
|
||||
std::vector<IdString> other_gnd_nets;
|
||||
std::vector<IdString> other_vcc_nets;
|
||||
|
||||
for (auto &net_pair : nets) {
|
||||
if (net_pair.first == gnd_net_name) {
|
||||
NPNR_ASSERT(net_pair.second.get() == gnd_net);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (net_pair.first == vcc_net_name) {
|
||||
NPNR_ASSERT(net_pair.second.get() == vcc_net);
|
||||
continue;
|
||||
}
|
||||
|
||||
NetInfo *net = net_pair.second.get();
|
||||
if (net->driver.cell == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (net->driver.cell->type == gnd_cell_type) {
|
||||
NPNR_ASSERT(net->driver.port == gnd_cell_port);
|
||||
|
||||
other_gnd_nets.push_back(net_pair.first);
|
||||
|
||||
if (need_gnd_source) {
|
||||
IdString driver_cell = net->driver.cell->name;
|
||||
disconnectPort(driver_cell, gnd_cell_port);
|
||||
connectPort(gnd_net_name, driver_cell, gnd_cell_port);
|
||||
need_gnd_source = false;
|
||||
}
|
||||
|
||||
NPNR_ASSERT(net->driver.port == gnd_cell_port);
|
||||
std::vector<PortRef> users_copy = net->users;
|
||||
for (const PortRef &port_ref : users_copy) {
|
||||
IdString cell = port_ref.cell->name;
|
||||
disconnectPort(cell, port_ref.port);
|
||||
connectPort(gnd_net_name, cell, port_ref.port);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (net->driver.cell->type == vcc_cell_type) {
|
||||
NPNR_ASSERT(net->driver.port == vcc_cell_port);
|
||||
|
||||
other_vcc_nets.push_back(net_pair.first);
|
||||
|
||||
if (need_vcc_source) {
|
||||
IdString driver_cell = net->driver.cell->name;
|
||||
disconnectPort(driver_cell, vcc_cell_port);
|
||||
connectPort(vcc_net_name, driver_cell, vcc_cell_port);
|
||||
need_vcc_source = false;
|
||||
}
|
||||
|
||||
NPNR_ASSERT(net->driver.port == vcc_cell_port);
|
||||
std::vector<PortRef> users_copy = net->users;
|
||||
for (const PortRef &port_ref : users_copy) {
|
||||
IdString cell = port_ref.cell->name;
|
||||
disconnectPort(cell, port_ref.port);
|
||||
connectPort(vcc_net_name, cell, port_ref.port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (IdString other_gnd_net : other_gnd_nets) {
|
||||
NetInfo *net = getNetByAlias(other_gnd_net);
|
||||
NPNR_ASSERT(net->users.empty());
|
||||
if (net->driver.cell) {
|
||||
PortRef driver = net->driver;
|
||||
IdString cell_to_remove = driver.cell->name;
|
||||
disconnectPort(driver.cell->name, driver.port);
|
||||
NPNR_ASSERT(cells.erase(cell_to_remove));
|
||||
}
|
||||
}
|
||||
|
||||
for (IdString other_vcc_net : other_vcc_nets) {
|
||||
NetInfo *net = getNetByAlias(other_vcc_net);
|
||||
NPNR_ASSERT(net->users.empty());
|
||||
if (net->driver.cell) {
|
||||
PortRef driver = net->driver;
|
||||
IdString cell_to_remove = driver.cell->name;
|
||||
disconnectPort(driver.cell->name, driver.port);
|
||||
NPNR_ASSERT(cells.erase(cell_to_remove));
|
||||
}
|
||||
}
|
||||
|
||||
for (IdString other_gnd_net : other_gnd_nets) {
|
||||
NPNR_ASSERT(nets.erase(other_gnd_net));
|
||||
gnd_net->aliases.push_back(other_gnd_net);
|
||||
net_aliases[other_gnd_net] = gnd_net_name;
|
||||
}
|
||||
|
||||
for (IdString other_vcc_net : other_vcc_nets) {
|
||||
NPNR_ASSERT(nets.erase(other_vcc_net));
|
||||
vcc_net->aliases.push_back(other_vcc_net);
|
||||
net_aliases[other_vcc_net] = vcc_net_name;
|
||||
}
|
||||
|
||||
if (need_gnd_source) {
|
||||
CellInfo *gnd_cell = createCell(gnd_cell_type, gnd_cell_type);
|
||||
gnd_cell->addOutput(gnd_cell_port);
|
||||
connectPort(gnd_net_name, gnd_cell_type, gnd_cell_port);
|
||||
}
|
||||
|
||||
if (need_vcc_source) {
|
||||
CellInfo *vcc_cell = createCell(vcc_cell_type, vcc_cell_type);
|
||||
vcc_cell->addOutput(vcc_cell_port);
|
||||
connectPort(vcc_net_name, vcc_cell_type, vcc_cell_port);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<IdString> &Arch::getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const
|
||||
{
|
||||
auto iter = cell_info->cell_bel_pins.find(pin);
|
||||
if (iter == cell_info->cell_bel_pins.end()) {
|
||||
return no_pins;
|
||||
} else {
|
||||
return iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
void Arch::report_invalid_bel(BelId bel, CellInfo *cell) const
|
||||
{
|
||||
int32_t mapping = bel_info(chip_info, bel).pin_map[get_cell_type_index(cell->type)];
|
||||
NPNR_ASSERT(mapping < 0);
|
||||
log_error("Cell %s (%s) cannot be placed at BEL %s (mapping %d)\n", cell->name.c_str(this), cell->type.c_str(this),
|
||||
nameOfBel(bel), mapping);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
@ -29,6 +29,8 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "constraints.h"
|
||||
#include "dedicated_interconnect.h"
|
||||
#include "site_router.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
@ -67,7 +69,7 @@ NPNR_PACKED_STRUCT(struct BelInfoPOD {
|
||||
int16_t site;
|
||||
int16_t site_variant; // some sites have alternative types
|
||||
int16_t category;
|
||||
int16_t padding;
|
||||
int16_t synthetic;
|
||||
|
||||
RelPtr<int32_t> pin_map; // Index into CellMapPOD::cell_bel_map
|
||||
});
|
||||
@ -120,8 +122,6 @@ NPNR_PACKED_STRUCT(struct ConstraintTagPOD {
|
||||
NPNR_PACKED_STRUCT(struct TileTypeInfoPOD {
|
||||
int32_t name; // Tile type constid
|
||||
|
||||
int32_t number_sites;
|
||||
|
||||
RelSlice<BelInfoPOD> bel_data;
|
||||
|
||||
RelSlice<TileWireInfoPOD> wire_data;
|
||||
@ -129,6 +129,8 @@ NPNR_PACKED_STRUCT(struct TileTypeInfoPOD {
|
||||
RelSlice<PipInfoPOD> pip_data;
|
||||
|
||||
RelSlice<ConstraintTagPOD> tags;
|
||||
|
||||
RelSlice<int32_t> site_types; // constid
|
||||
});
|
||||
|
||||
NPNR_PACKED_STRUCT(struct SiteInstInfoPOD {
|
||||
@ -147,7 +149,7 @@ NPNR_PACKED_STRUCT(struct TileInstInfoPOD {
|
||||
// Index into root.tile_types.
|
||||
int32_t type;
|
||||
|
||||
// This array is root.tile_types[type].number_sites long.
|
||||
// This array is root.tile_types[type].site_types.size() long.
|
||||
// Index into root.sites
|
||||
RelSlice<int32_t> sites;
|
||||
|
||||
@ -207,6 +209,29 @@ NPNR_PACKED_STRUCT(struct PackagePOD {
|
||||
RelSlice<PackagePinPOD> pins;
|
||||
});
|
||||
|
||||
NPNR_PACKED_STRUCT(struct ConstantsPOD {
|
||||
// Cell type and port for the GND and VCC global source.
|
||||
int32_t gnd_cell_name; // constid
|
||||
int32_t gnd_cell_port; // constid
|
||||
|
||||
int32_t vcc_cell_name; // constid
|
||||
int32_t vcc_cell_port; // constid
|
||||
|
||||
int32_t gnd_bel_tile;
|
||||
int32_t gnd_bel_index;
|
||||
int32_t gnd_bel_pin; // constid
|
||||
|
||||
int32_t vcc_bel_tile;
|
||||
int32_t vcc_bel_index;
|
||||
int32_t vcc_bel_pin; // constid
|
||||
|
||||
// Name to use for the global GND constant net
|
||||
int32_t gnd_net_name; // constid
|
||||
|
||||
// Name to use for the global VCC constant net
|
||||
int32_t vcc_net_name; // constid
|
||||
});
|
||||
|
||||
NPNR_PACKED_STRUCT(struct ChipInfoPOD {
|
||||
RelPtr<char> name;
|
||||
RelPtr<char> generator;
|
||||
@ -224,6 +249,7 @@ NPNR_PACKED_STRUCT(struct ChipInfoPOD {
|
||||
RelSlice<int32_t> bel_buckets;
|
||||
|
||||
RelPtr<CellMapPOD> cell_map;
|
||||
RelPtr<ConstantsPOD> constants;
|
||||
|
||||
// Constid string data.
|
||||
RelPtr<RelSlice<RelPtr<char>>> constids;
|
||||
@ -748,6 +774,15 @@ struct ArchRanges
|
||||
using BucketBelRangeT = FilteredBelRange;
|
||||
};
|
||||
|
||||
static constexpr size_t kMaxState = 8;
|
||||
|
||||
struct TileStatus
|
||||
{
|
||||
std::vector<ExclusiveStateGroup<kMaxState>> tags;
|
||||
std::vector<CellInfo *> boundcells;
|
||||
std::vector<SiteRouter> sites;
|
||||
};
|
||||
|
||||
struct Arch : ArchAPI<ArchRanges>
|
||||
{
|
||||
boost::iostreams::mapped_file_source blob_file;
|
||||
@ -759,38 +794,13 @@ struct Arch : ArchAPI<ArchRanges>
|
||||
|
||||
std::unordered_map<WireId, NetInfo *> wire_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;
|
||||
|
||||
struct TileStatus;
|
||||
struct SiteRouter
|
||||
{
|
||||
SiteRouter(int16_t site) : site(site), dirty(false), site_ok(true) {}
|
||||
|
||||
std::unordered_set<CellInfo *> cells_in_site;
|
||||
const int16_t site;
|
||||
|
||||
mutable bool dirty;
|
||||
mutable bool site_ok;
|
||||
|
||||
void bindBel(CellInfo *cell);
|
||||
void unbindBel(CellInfo *cell);
|
||||
bool checkSiteRouting(const Context *ctx, const TileStatus &tile_status) const;
|
||||
};
|
||||
|
||||
struct TileStatus
|
||||
{
|
||||
std::vector<ExclusiveStateGroup<kMaxState>> tags;
|
||||
std::vector<CellInfo *> boundcells;
|
||||
std::vector<SiteRouter> sites;
|
||||
};
|
||||
|
||||
DedicatedInterconnect dedicated_interconnect;
|
||||
std::unordered_map<int32_t, TileStatus> tileStatus;
|
||||
|
||||
ArchArgs args;
|
||||
Arch(ArchArgs args);
|
||||
void init();
|
||||
|
||||
std::string getChipName() const override;
|
||||
|
||||
@ -822,7 +832,7 @@ struct Arch : ArchAPI<ArchRanges>
|
||||
}
|
||||
int getTilePipDimZ(int x, int y) const override
|
||||
{
|
||||
return chip_info->tile_types[chip_info->tiles[get_tile_index(x, y)].type].number_sites;
|
||||
return chip_info->tile_types[chip_info->tiles[get_tile_index(x, y)].type].site_types.size();
|
||||
}
|
||||
char getNameDelimiter() const override { return '/'; }
|
||||
|
||||
@ -844,7 +854,7 @@ struct Arch : ArchAPI<ArchRanges>
|
||||
|
||||
uint32_t getBelChecksum(BelId bel) const override { return bel.index; }
|
||||
|
||||
void map_cell_pins(CellInfo *cell, int32_t mapping) const;
|
||||
void map_cell_pins(CellInfo *cell, int32_t mapping, bool bind_constants);
|
||||
void map_port_pins(BelId bel, CellInfo *cell) const;
|
||||
|
||||
TileStatus &get_tile_status(int32_t tile)
|
||||
@ -855,8 +865,8 @@ struct Arch : ArchAPI<ArchRanges>
|
||||
result.first->second.boundcells.resize(tile_type.bel_data.size());
|
||||
result.first->second.tags.resize(default_tags.size());
|
||||
|
||||
result.first->second.sites.reserve(tile_type.number_sites);
|
||||
for (size_t i = 0; i < (size_t)tile_type.number_sites; ++i) {
|
||||
result.first->second.sites.reserve(tile_type.site_types.size());
|
||||
for (size_t i = 0; i < tile_type.site_types.size(); ++i) {
|
||||
result.first->second.sites.push_back(SiteRouter(i));
|
||||
}
|
||||
}
|
||||
@ -874,6 +884,24 @@ struct Arch : ArchAPI<ArchRanges>
|
||||
return tile_status.sites.at(bel_data.site);
|
||||
}
|
||||
|
||||
BelId get_vcc_bel() const
|
||||
{
|
||||
auto &constants = *chip_info->constants;
|
||||
BelId bel;
|
||||
bel.tile = constants.vcc_bel_tile;
|
||||
bel.index = constants.vcc_bel_index;
|
||||
return bel;
|
||||
}
|
||||
|
||||
BelId get_gnd_bel() const
|
||||
{
|
||||
auto &constants = *chip_info->constants;
|
||||
BelId bel;
|
||||
bel.tile = constants.gnd_bel_tile;
|
||||
bel.index = constants.gnd_bel_index;
|
||||
return bel;
|
||||
}
|
||||
|
||||
void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) override
|
||||
{
|
||||
NPNR_ASSERT(bel != BelId());
|
||||
@ -886,10 +914,13 @@ struct Arch : ArchAPI<ArchRanges>
|
||||
|
||||
if (io_port_types.count(cell->type) == 0) {
|
||||
int32_t mapping = bel_info(chip_info, bel).pin_map[get_cell_type_index(cell->type)];
|
||||
if (mapping < 0) {
|
||||
report_invalid_bel(bel, cell);
|
||||
}
|
||||
NPNR_ASSERT(mapping >= 0);
|
||||
|
||||
if (cell->cell_mapping != mapping) {
|
||||
map_cell_pins(cell, mapping);
|
||||
map_cell_pins(cell, mapping, /*bind_constants=*/false);
|
||||
}
|
||||
constraints.bindBel(tile_status.tags.data(), get_cell_constraints(bel, cell->type));
|
||||
} else {
|
||||
@ -1033,10 +1064,7 @@ struct Arch : ArchAPI<ArchRanges>
|
||||
return str_range;
|
||||
}
|
||||
|
||||
const std::vector<IdString> &getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const override
|
||||
{
|
||||
return cell_info->cell_bel_pins.at(pin);
|
||||
}
|
||||
const std::vector<IdString> &getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const override;
|
||||
|
||||
// -------------------------------------------------
|
||||
|
||||
@ -1194,9 +1222,6 @@ struct Arch : ArchAPI<ArchRanges>
|
||||
NPNR_ASSERT(wire_to_net[dst] == nullptr || wire_to_net[dst] == 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;
|
||||
net->wires[dst].pip = pip;
|
||||
@ -1467,6 +1492,10 @@ struct Arch : ArchAPI<ArchRanges>
|
||||
if (cell == nullptr) {
|
||||
return true;
|
||||
} else {
|
||||
if (!dedicated_interconnect.isBelLocationValid(bel, cell)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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
|
||||
@ -1642,6 +1671,41 @@ struct Arch : ArchAPI<ArchRanges>
|
||||
auto &pip_data = pip_info(chip_info, pip);
|
||||
return site_inst_info(chip_info, pip.tile, pip_data.site);
|
||||
}
|
||||
|
||||
// Is this bel synthetic (e.g. added during import process)?
|
||||
//
|
||||
// This is generally used for constant networks, but can also be used for
|
||||
// static partitions.
|
||||
bool is_bel_synthetic(BelId bel) const
|
||||
{
|
||||
const BelInfoPOD &bel_data = bel_info(chip_info, bel);
|
||||
|
||||
return bel_data.synthetic != 0;
|
||||
}
|
||||
|
||||
// Is this pip synthetic (e.g. added during import process)?
|
||||
//
|
||||
// This is generally used for constant networks, but can also be used for
|
||||
// static partitions.
|
||||
bool is_pip_synthetic(PipId pip) const
|
||||
{
|
||||
auto &pip_data = pip_info(chip_info, pip);
|
||||
if (pip_data.site == -1) {
|
||||
return pip_data.extra_data == -1;
|
||||
} else {
|
||||
BelId bel;
|
||||
bel.tile = pip.tile;
|
||||
bel.index = pip_data.bel;
|
||||
return is_bel_synthetic(bel);
|
||||
}
|
||||
}
|
||||
|
||||
void merge_constant_nets();
|
||||
void report_invalid_bel(BelId bel, CellInfo *cell) const;
|
||||
|
||||
std::vector<IdString> no_pins;
|
||||
IdString gnd_cell_pin;
|
||||
IdString vcc_cell_pin;
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
@ -243,7 +243,9 @@ void Arch::pack_ports()
|
||||
|
||||
for (CellInfo *cell : placed_cells) {
|
||||
NPNR_ASSERT(cell->bel != BelId());
|
||||
NPNR_ASSERT(isBelLocationValid(cell->bel));
|
||||
if (!isBelLocationValid(cell->bel)) {
|
||||
log_error("Tightly bound BEL %s was not valid!\n", nameOfBel(cell->bel));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,6 +106,7 @@ struct ArchCellInfo
|
||||
|
||||
int32_t cell_mapping;
|
||||
std::unordered_map<IdString, std::vector<IdString>> cell_bel_pins;
|
||||
std::unordered_set<IdString> const_ports;
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
785
fpga_interchange/dedicated_interconnect.cc
Normal file
785
fpga_interchange/dedicated_interconnect.cc
Normal file
@ -0,0 +1,785 @@
|
||||
/*
|
||||
* 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"
|
||||
#include "util.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
// 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::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
|
||||
{
|
||||
std::vector<WireNode> nodes_to_expand;
|
||||
|
||||
WireId src_wire = ctx->getBelPinWire(src_bel, src_bel_pin);
|
||||
|
||||
const auto &src_wire_data = ctx->wire_info(src_wire);
|
||||
NPNR_ASSERT(src_wire_data.site != -1);
|
||||
|
||||
WireId dst_wire = ctx->getBelPinWire(dst_bel, dst_bel_pin);
|
||||
|
||||
const auto &dst_wire_data = ctx->wire_info(dst_wire);
|
||||
NPNR_ASSERT(dst_wire_data.site != -1);
|
||||
|
||||
WireNode wire_node;
|
||||
wire_node.wire = src_wire;
|
||||
wire_node.state = IN_SOURCE_SITE;
|
||||
wire_node.depth = 0;
|
||||
|
||||
nodes_to_expand.push_back(wire_node);
|
||||
|
||||
while (!nodes_to_expand.empty()) {
|
||||
WireNode node_to_expand = nodes_to_expand.back();
|
||||
nodes_to_expand.pop_back();
|
||||
|
||||
for (PipId pip : ctx->getPipsDownhill(node_to_expand.wire)) {
|
||||
if (ctx->is_pip_synthetic(pip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WireId wire = ctx->getPipDstWire(pip);
|
||||
if (wire == WireId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_EXPANSION
|
||||
log_info(" - At wire %s via %s\n", ctx->nameOfWire(wire), ctx->nameOfPip(pip));
|
||||
#endif
|
||||
|
||||
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 false;
|
||||
}
|
||||
|
||||
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_SOURCE_SITE:
|
||||
NPNR_ASSERT(wire_data.site == -1);
|
||||
next_node.state = IN_ROUTING;
|
||||
break;
|
||||
case IN_ROUTING:
|
||||
NPNR_ASSERT(wire_data.site != -1);
|
||||
if (wire.tile == src_wire.tile && wire_data.site == src_wire_data.site) {
|
||||
// Dedicated routing won't have straight loops,
|
||||
// general routing looks like that.
|
||||
#ifdef DEBUG_EXPANSION
|
||||
log_info(" - Not dedicated site routing because loop!");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
next_node.state = IN_SINK_SITE;
|
||||
break;
|
||||
case IN_SINK_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_SINK_SITE) {
|
||||
for (BelPin bel_pin : ctx->getWireBelPins(wire)) {
|
||||
if (bel_pin.bel == dst_bel && bel_pin.pin == dst_bel_pin) {
|
||||
if (ctx->debug) {
|
||||
log_info("Valid dedicated interconnect from %s/%s to %s/%s\n", ctx->nameOfBel(src_bel),
|
||||
src_bel_pin.c_str(ctx), ctx->nameOfBel(dst_bel), dst_bel_pin.c_str(ctx));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DedicatedInterconnect::is_driver_on_net_valid(BelId driver_bel, const CellInfo *cell, IdString driver_port,
|
||||
NetInfo *net) const
|
||||
{
|
||||
const auto &driver_bel_data = bel_info(ctx->chip_info, driver_bel);
|
||||
|
||||
TileTypeBelPin type_bel_pin;
|
||||
type_bel_pin.tile_type = ctx->chip_info->tiles[driver_bel.tile].type;
|
||||
type_bel_pin.bel_index = driver_bel.index;
|
||||
|
||||
Loc driver_loc = ctx->getBelLocation(driver_bel);
|
||||
|
||||
for (IdString driver_bel_pin : ctx->getBelPinsForCellPin(cell, driver_port)) {
|
||||
type_bel_pin.bel_pin = driver_bel_pin;
|
||||
|
||||
auto iter = sources.find(type_bel_pin);
|
||||
if (iter == sources.end()) {
|
||||
// This BEL pin doesn't have a dedicate interconnect.
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const PortRef &port_ref : net->users) {
|
||||
NPNR_ASSERT(port_ref.cell != nullptr);
|
||||
|
||||
if (port_ref.cell->bel == BelId()) {
|
||||
// FIXME: This should actually return "unknown!" because the
|
||||
// sink is unplaced. Once the sink is placed, this constraint
|
||||
// can be evaluated.
|
||||
if (ctx->debug) {
|
||||
log_info("BEL %s is not valid because sink cell %s/%s is not placed\n", ctx->nameOfBel(driver_bel),
|
||||
port_ref.cell->name.c_str(ctx), port_ref.port.c_str(ctx));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
BelId sink_bel = port_ref.cell->bel;
|
||||
const auto &sink_bel_data = bel_info(ctx->chip_info, sink_bel);
|
||||
Loc sink_loc = ctx->getBelLocation(port_ref.cell->bel);
|
||||
|
||||
if (sink_bel.tile == driver_bel.tile && sink_bel_data.site == driver_bel_data.site) {
|
||||
// This is a site local routing, even though this is a sink
|
||||
// with a dedicated interconnect.
|
||||
continue;
|
||||
}
|
||||
|
||||
DeltaTileTypeBelPin sink_type_bel_pin;
|
||||
sink_type_bel_pin.delta_x = sink_loc.x - driver_loc.x;
|
||||
sink_type_bel_pin.delta_y = sink_loc.y - driver_loc.y;
|
||||
sink_type_bel_pin.type_bel_pin.tile_type = ctx->chip_info->tiles[sink_bel.tile].type;
|
||||
sink_type_bel_pin.type_bel_pin.bel_index = sink_bel.index;
|
||||
|
||||
for (IdString sink_bel_pin : ctx->getBelPinsForCellPin(port_ref.cell, port_ref.port)) {
|
||||
sink_type_bel_pin.type_bel_pin.bel_pin = sink_bel_pin;
|
||||
|
||||
// Do fast routing check to see if the pair of driver and sink
|
||||
// every are valid.
|
||||
if (iter->second.count(sink_type_bel_pin) == 0) {
|
||||
if (ctx->debug) {
|
||||
log_info("BEL %s is not valid because pin %s cannot reach %s/%s\n", ctx->nameOfBel(driver_bel),
|
||||
driver_bel_pin.c_str(ctx), ctx->nameOfBel(sink_bel), sink_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_bel_pin, sink_bel, sink_bel_pin)) {
|
||||
if (ctx->debug) {
|
||||
log_info("BEL %s is not valid because pin %s cannot be reach %s/%s (via detailed check)\n",
|
||||
ctx->nameOfBel(driver_bel), driver_bel_pin.c_str(ctx), ctx->nameOfBel(sink_bel),
|
||||
sink_bel_pin.c_str(ctx));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DedicatedInterconnect::is_sink_on_net_valid(BelId bel, const CellInfo *cell, IdString port_name,
|
||||
NetInfo *net) const
|
||||
{
|
||||
const auto &bel_data = bel_info(ctx->chip_info, bel);
|
||||
Loc bel_loc = ctx->getBelLocation(bel);
|
||||
|
||||
BelId driver_bel = net->driver.cell->bel;
|
||||
|
||||
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 = sinks.find(type_bel_pin);
|
||||
if (iter == sinks.end()) {
|
||||
// This BEL pin doesn't have a dedicate interconnect.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (driver_bel == BelId()) {
|
||||
// FIXME: This should actually return "unknown!" because the
|
||||
// driver is unplaced. Once the driver is placed, this constraint
|
||||
// can be evaluated.
|
||||
if (ctx->debug) {
|
||||
log_info("BEL %s is not valid because driver cell %s/%s is not placed\n", ctx->nameOfBel(bel),
|
||||
net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &driver_bel_data = bel_info(ctx->chip_info, driver_bel);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// 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->debug) {
|
||||
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->debug) {
|
||||
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;
|
||||
}
|
||||
|
||||
bool DedicatedInterconnect::isBelLocationValid(BelId bel, const CellInfo *cell) const
|
||||
{
|
||||
NPNR_ASSERT(bel != BelId());
|
||||
|
||||
for (const auto &port_pair : cell->ports) {
|
||||
IdString port_name = port_pair.first;
|
||||
NetInfo *net = port_pair.second.net;
|
||||
if (net == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// This net doesn't have a driver, probably not valid?
|
||||
NPNR_ASSERT(net->driver.cell != nullptr);
|
||||
|
||||
// Only check sink BELs.
|
||||
if (net->driver.cell == cell && net->driver.port == port_name) {
|
||||
if (!is_driver_on_net_valid(bel, cell, port_name, net)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!is_sink_on_net_valid(bel, cell, port_name, net)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DedicatedInterconnect::print_dedicated_interconnect() const
|
||||
{
|
||||
log_info("Found %zu sinks with dedicated interconnect\n", sinks.size());
|
||||
log_info("Found %zu sources with dedicated interconnect\n", sources.size());
|
||||
std::vector<TileTypeBelPin> sorted_keys;
|
||||
for (const auto &sink_to_srcs : sinks) {
|
||||
sorted_keys.push_back(sink_to_srcs.first);
|
||||
}
|
||||
for (const auto &src_to_sinks : sources) {
|
||||
sorted_keys.push_back(src_to_sinks.first);
|
||||
}
|
||||
std::sort(sorted_keys.begin(), sorted_keys.end());
|
||||
|
||||
for (const auto &key : sorted_keys) {
|
||||
auto iter = sinks.find(key);
|
||||
if (iter != sinks.end()) {
|
||||
auto dst = key;
|
||||
for (const auto &src_delta : iter->second) {
|
||||
auto src = src_delta.type_bel_pin;
|
||||
auto delta_x = src_delta.delta_x;
|
||||
auto delta_y = src_delta.delta_y;
|
||||
|
||||
const TileTypeInfoPOD &src_tile_type = ctx->chip_info->tile_types[src.tile_type];
|
||||
const BelInfoPOD &src_bel_info = src_tile_type.bel_data[src.bel_index];
|
||||
IdString src_site_type = IdString(src_tile_type.site_types[src_bel_info.site]);
|
||||
IdString src_bel_pin = src.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[%d]/%s/%s (%d, %d) -> %s.%s[%d]/%s/%s\n", IdString(src_tile_type.name).c_str(ctx),
|
||||
src_site_type.c_str(ctx), src_bel_info.site, IdString(src_bel_info.name).c_str(ctx),
|
||||
src_bel_pin.c_str(ctx), delta_x, delta_y, IdString(dst_tile_type.name).c_str(ctx),
|
||||
dst_site_type.c_str(ctx), dst_bel_info.site, IdString(dst_bel_info.name).c_str(ctx),
|
||||
dst_bel_pin.c_str(ctx));
|
||||
}
|
||||
} else {
|
||||
auto src = key;
|
||||
for (const auto &dst_delta : sources.at(key)) {
|
||||
auto dst = dst_delta.type_bel_pin;
|
||||
auto delta_x = dst_delta.delta_x;
|
||||
auto delta_y = dst_delta.delta_y;
|
||||
|
||||
const TileTypeInfoPOD &src_tile_type = ctx->chip_info->tile_types[src.tile_type];
|
||||
const BelInfoPOD &src_bel_info = src_tile_type.bel_data[src.bel_index];
|
||||
IdString src_site_type = IdString(src_tile_type.site_types[src_bel_info.site]);
|
||||
IdString src_bel_pin = src.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[%d]/%s/%s -> %s.%s[%d]/%s/%s (%d, %d)\n", IdString(src_tile_type.name).c_str(ctx),
|
||||
src_site_type.c_str(ctx), src_bel_info.site, IdString(src_bel_info.name).c_str(ctx),
|
||||
src_bel_pin.c_str(ctx), IdString(dst_tile_type.name).c_str(ctx), dst_site_type.c_str(ctx),
|
||||
dst_bel_info.site, IdString(dst_bel_info.name).c_str(ctx), dst_bel_pin.c_str(ctx), delta_x,
|
||||
delta_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_sink_bel(bel, IdString(bel_data.ports[i]), wire);
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<TileTypeBelPin> seen_pins;
|
||||
for (auto sink_pair : sinks) {
|
||||
for (auto src : sink_pair.second) {
|
||||
seen_pins.emplace(src.type_bel_pin);
|
||||
}
|
||||
}
|
||||
|
||||
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_OUT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IdString bel_pin(bel_data.ports[i]);
|
||||
|
||||
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;
|
||||
|
||||
// Don't visit src pins already handled in the sink expansion!
|
||||
if (seen_pins.count(type_bel_pin)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WireId wire;
|
||||
wire.tile = bel.tile;
|
||||
wire.index = bel_data.wires[i];
|
||||
|
||||
expand_source_bel(bel, bel_pin, wire);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DedicatedInterconnect::expand_sink_bel(BelId sink_bel, IdString sink_pin, WireId sink_wire)
|
||||
{
|
||||
NPNR_ASSERT(sink_bel != BelId());
|
||||
#ifdef DEBUG_EXPANSION
|
||||
log_info("Expanding from %s/%s\n", ctx->nameOfBel(sink_bel), pin.c_str(ctx));
|
||||
#endif
|
||||
|
||||
std::vector<WireNode> nodes_to_expand;
|
||||
|
||||
const auto &sink_wire_data = ctx->wire_info(sink_wire);
|
||||
NPNR_ASSERT(sink_wire_data.site != -1);
|
||||
|
||||
WireNode wire_node;
|
||||
wire_node.wire = sink_wire;
|
||||
wire_node.state = IN_SINK_SITE;
|
||||
wire_node.depth = 0;
|
||||
|
||||
nodes_to_expand.push_back(wire_node);
|
||||
|
||||
Loc sink_loc = ctx->getBelLocation(sink_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;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_EXPANSION
|
||||
log_info(" - At wire %s via %s\n", ctx->nameOfWire(wire), ctx->nameOfPip(pip));
|
||||
#endif
|
||||
|
||||
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?
|
||||
#ifdef DEBUG_EXPANSION
|
||||
log_info(" - Exceeded max depth!\n");
|
||||
#endif
|
||||
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.tile == sink_wire.tile && wire_data.site == sink_wire_data.site) {
|
||||
// Dedicated routing won't have straight loops,
|
||||
// general routing looks like that.
|
||||
#ifdef DEBUG_EXPANSION
|
||||
log_info(" - Not dedicated site routing because loop!");
|
||||
#endif
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_EXPANSION
|
||||
log_info(" - Reached %s/%s\n", ctx->nameOfBel(bel_pin.bel), bel_pin.pin.c_str(ctx));
|
||||
#endif
|
||||
|
||||
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_y = 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[sink_bel.tile].type;
|
||||
type_bel_pin.bel_index = sink_bel.index;
|
||||
type_bel_pin.bel_pin = sink_pin;
|
||||
|
||||
auto result = sinks.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DedicatedInterconnect::expand_source_bel(BelId src_bel, IdString src_pin, WireId src_wire)
|
||||
{
|
||||
NPNR_ASSERT(src_bel != BelId());
|
||||
#ifdef DEBUG_EXPANSION
|
||||
log_info("Expanding from %s/%s\n", ctx->nameOfBel(src_bel), src_pin.c_str(ctx));
|
||||
#endif
|
||||
|
||||
std::vector<WireNode> nodes_to_expand;
|
||||
|
||||
const auto &src_wire_data = ctx->wire_info(src_wire);
|
||||
NPNR_ASSERT(src_wire_data.site != -1);
|
||||
|
||||
WireNode wire_node;
|
||||
wire_node.wire = src_wire;
|
||||
wire_node.state = IN_SOURCE_SITE;
|
||||
wire_node.depth = 0;
|
||||
|
||||
nodes_to_expand.push_back(wire_node);
|
||||
|
||||
Loc src_loc = ctx->getBelLocation(src_bel);
|
||||
std::unordered_set<DeltaTileTypeBelPin> dsts;
|
||||
|
||||
while (!nodes_to_expand.empty()) {
|
||||
WireNode node_to_expand = nodes_to_expand.back();
|
||||
nodes_to_expand.pop_back();
|
||||
|
||||
for (PipId pip : ctx->getPipsDownhill(node_to_expand.wire)) {
|
||||
if (ctx->is_pip_synthetic(pip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WireId wire = ctx->getPipDstWire(pip);
|
||||
if (wire == WireId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_EXPANSION
|
||||
log_info(" - At wire %s via %s\n", ctx->nameOfWire(wire), ctx->nameOfPip(pip));
|
||||
#endif
|
||||
|
||||
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?
|
||||
#ifdef DEBUG_EXPANSION
|
||||
log_info(" - Exceeded max depth!\n");
|
||||
#endif
|
||||
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_SOURCE_SITE:
|
||||
NPNR_ASSERT(wire_data.site == -1);
|
||||
next_node.state = IN_ROUTING;
|
||||
break;
|
||||
case IN_ROUTING:
|
||||
NPNR_ASSERT(wire_data.site != -1);
|
||||
if (wire.tile == src_wire.tile && wire_data.site == src_wire_data.site) {
|
||||
// Dedicated routing won't have straight loops,
|
||||
// general routing looks like that.
|
||||
#ifdef DEBUG_EXPANSION
|
||||
log_info(" - Not dedicated site routing because loop!");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
next_node.state = IN_SINK_SITE;
|
||||
break;
|
||||
case IN_SINK_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_SINK_SITE) {
|
||||
for (BelPin bel_pin : ctx->getWireBelPins(wire)) {
|
||||
BelId sink_bel = bel_pin.bel;
|
||||
auto const &bel_data = bel_info(ctx->chip_info, sink_bel);
|
||||
|
||||
if (bel_data.category != BEL_CATEGORY_LOGIC) {
|
||||
continue;
|
||||
}
|
||||
if (bel_data.synthetic) {
|
||||
continue;
|
||||
}
|
||||
if (ctx->getBelPinType(bel_pin.bel, bel_pin.pin) != PORT_IN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_EXPANSION
|
||||
log_info(" - Reached %s/%s\n", ctx->nameOfBel(bel_pin.bel), bel_pin.pin.c_str(ctx));
|
||||
#endif
|
||||
|
||||
Loc sink_loc = ctx->getBelLocation(sink_bel);
|
||||
|
||||
DeltaTileTypeBelPin delta_type_bel_pin;
|
||||
delta_type_bel_pin.delta_x = sink_loc.x - src_loc.x;
|
||||
delta_type_bel_pin.delta_y = sink_loc.y - src_loc.y;
|
||||
delta_type_bel_pin.type_bel_pin.tile_type = ctx->chip_info->tiles[sink_bel.tile].type;
|
||||
delta_type_bel_pin.type_bel_pin.bel_index = sink_bel.index;
|
||||
delta_type_bel_pin.type_bel_pin.bel_pin = bel_pin.pin;
|
||||
dsts.emplace(delta_type_bel_pin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TileTypeBelPin type_bel_pin;
|
||||
type_bel_pin.tile_type = ctx->chip_info->tiles[src_bel.tile].type;
|
||||
type_bel_pin.bel_index = src_bel.index;
|
||||
type_bel_pin.bel_pin = src_pin;
|
||||
|
||||
auto result = sources.emplace(type_bel_pin, dsts);
|
||||
if (!result.second) {
|
||||
// type_bel_pin was already present! Add any new sources from this
|
||||
// sink type (if any);
|
||||
for (auto dst : dsts) {
|
||||
result.first->second.emplace(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
140
fpga_interchange/dedicated_interconnect.h
Normal file
140
fpga_interchange/dedicated_interconnect.h
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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>> sinks;
|
||||
std::unordered_map<TileTypeBelPin, std::unordered_set<DeltaTileTypeBelPin>> sources;
|
||||
|
||||
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_sink_bel(BelId bel, IdString pin, WireId wire);
|
||||
void expand_source_bel(BelId bel, IdString pin, WireId wire);
|
||||
|
||||
bool is_driver_on_net_valid(BelId driver_bel, const CellInfo *cell, IdString driver_port, NetInfo *net) const;
|
||||
bool is_sink_on_net_valid(BelId bel, const CellInfo *cell, IdString port_name, NetInfo *net) const;
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
@ -13,4 +13,11 @@ check: check_test_data
|
||||
check_test_data:
|
||||
$(NEXTPNR_BIN) \
|
||||
--chipdb $(BBA_PATH) \
|
||||
--package $(PACKAGE) \
|
||||
--run $(NEXTPNR_PATH)/python/check_arch_api.py
|
||||
|
||||
debug_check_test_data:
|
||||
gdb --args $(NEXTPNR_BIN) \
|
||||
--chipdb $(BBA_PATH) \
|
||||
--package $(PACKAGE) \
|
||||
--run $(NEXTPNR_PATH)/python/check_arch_api.py
|
||||
|
@ -1,7 +1,36 @@
|
||||
pip_test:
|
||||
- src_wire: CLBLM_R_X11Y93/CLBLM_L_D3
|
||||
dst_wire: SLICE_X15Y93.SLICEL/D3
|
||||
pip_chain_test:
|
||||
- wires:
|
||||
- $CONSTANTS_X0Y0.$CONSTANTS/$GND_SOURCE
|
||||
- $CONSTANTS_X0Y0/$GND_NODE
|
||||
- TIEOFF_X3Y145.TIEOFF/$GND_SITE_WIRE
|
||||
- TIEOFF_X3Y145.TIEOFF/HARD0GND_HARD0
|
||||
- INT_R_X3Y145/GND_WIRE
|
||||
- wires:
|
||||
- $CONSTANTS_X0Y0.$CONSTANTS/$VCC_SOURCE
|
||||
- $CONSTANTS_X0Y0/$VCC_NODE
|
||||
- TIEOFF_X3Y145.TIEOFF/$VCC_SITE_WIRE
|
||||
- TIEOFF_X3Y145.TIEOFF/HARD1VCC_HARD1
|
||||
- INT_R_X3Y145/VCC_WIRE
|
||||
- wires:
|
||||
- $CONSTANTS_X0Y0.$CONSTANTS/$VCC_SOURCE
|
||||
- $CONSTANTS_X0Y0/$VCC_NODE
|
||||
- SLICE_X3Y145.SLICEL/$VCC_SITE_WIRE
|
||||
- SLICE_X3Y145.SLICEL/CEUSEDVCC_HARD1
|
||||
- wires:
|
||||
- $CONSTANTS_X0Y0.$CONSTANTS/$GND_SOURCE
|
||||
- $CONSTANTS_X0Y0/$GND_NODE
|
||||
- SLICE_X3Y145.SLICEL/$GND_SITE_WIRE
|
||||
- SLICE_X3Y145.SLICEL/SRUSEDGND_HARD0
|
||||
bel_pin_test:
|
||||
- bel: SLICE_X15Y93.SLICEL/D6LUT
|
||||
pin: A3
|
||||
wire: SLICE_X15Y93.SLICEL/D3
|
||||
- bel: $CONSTANTS_X0Y0.$CONSTANTS/GND
|
||||
pin: G
|
||||
wire: $CONSTANTS_X0Y0.$CONSTANTS/$GND_SOURCE
|
||||
- bel: $CONSTANTS_X0Y0.$CONSTANTS/VCC
|
||||
pin: P
|
||||
wire: $CONSTANTS_X0Y0.$CONSTANTS/$VCC_SOURCE
|
||||
|
8
fpga_interchange/examples/const_wire/Makefile
Normal file
8
fpga_interchange/examples/const_wire/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
DESIGN := wire
|
||||
DESIGN_TOP := top
|
||||
PACKAGE := csg324
|
||||
|
||||
include ../template.mk
|
||||
|
||||
build/wire.json: wire.v | build
|
||||
yosys -c run.tcl
|
14
fpga_interchange/examples/const_wire/run.tcl
Normal file
14
fpga_interchange/examples/const_wire/run.tcl
Normal file
@ -0,0 +1,14 @@
|
||||
yosys -import
|
||||
|
||||
read_verilog wire.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/wire.json
|
8
fpga_interchange/examples/const_wire/wire.v
Normal file
8
fpga_interchange/examples/const_wire/wire.v
Normal file
@ -0,0 +1,8 @@
|
||||
module top(output o, output o2, output o3, output o4);
|
||||
|
||||
assign o = 1'b0;
|
||||
assign o2 = 1'b1;
|
||||
assign o3 = 1'b0;
|
||||
assign o4 = 1'b1;
|
||||
|
||||
endmodule
|
9
fpga_interchange/examples/const_wire/wire.xdc
Normal file
9
fpga_interchange/examples/const_wire/wire.xdc
Normal file
@ -0,0 +1,9 @@
|
||||
set_property PACKAGE_PIN N15 [get_ports o]
|
||||
set_property PACKAGE_PIN N16 [get_ports o2]
|
||||
set_property PACKAGE_PIN P17 [get_ports o3]
|
||||
set_property PACKAGE_PIN R17 [get_ports o4]
|
||||
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports o]
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports o2]
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports o3]
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports o4]
|
@ -30,7 +30,7 @@ include ../common.mk
|
||||
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
.PHONY: all chipdb
|
||||
.PHONY: all chipdb test debug_test
|
||||
|
||||
all: chipdb
|
||||
|
||||
@ -38,11 +38,7 @@ build:
|
||||
mkdir build
|
||||
|
||||
build/RapidWright: | build
|
||||
# FIXME: Update URL / branch as fixes are merged upstream and / or
|
||||
# interchange branch on Xilinx/RapidWright is merged to master branch.
|
||||
#
|
||||
#cd build && git clone -b interchange https://github.com/Xilinx/RapidWright.git
|
||||
cd build && git clone -b move_strlist https://github.com/litghost/RapidWright.git
|
||||
cd build && git clone https://github.com/Xilinx/RapidWright.git
|
||||
|
||||
build/env: | build
|
||||
python3 -mvenv build/env
|
||||
@ -66,7 +62,7 @@ $(NEXTPNR_PATH)/build/bba/bbasm: | $(NEXTPNR_PATH)/build
|
||||
cd $(NEXTPNR_PATH)/build && cmake -DARCH=fpga_interchange ..
|
||||
make -j -C $(NEXTPNR_PATH)/build
|
||||
|
||||
$(NEXTPNR_PATH)/fpga_interchange/chipdb.bba: build/.setup
|
||||
build/nextpnr/fpga_interchange/chipdb.bba: build/.setup
|
||||
mkdir -p build/nextpnr/fpga_interchange
|
||||
source build/env/bin/activate && \
|
||||
cd build/python-fpga-interchange/ && \
|
||||
@ -76,7 +72,7 @@ $(NEXTPNR_PATH)/fpga_interchange/chipdb.bba: build/.setup
|
||||
RAPIDWRIGHT_PATH=$(RAPIDWRIGHT_PATH) \
|
||||
INTERCHANGE_PATH=$(INTERCHANGE_PATH)
|
||||
|
||||
$(BBA_PATH): $(NEXTPNR_PATH)/build/bba/bbasm $(NEXTPNR_PATH)/fpga_interchange/chipdb.bba
|
||||
$(BBA_PATH): $(NEXTPNR_PATH)/build/bba/bbasm build/nextpnr/fpga_interchange/chipdb.bba
|
||||
$(NEXTPNR_PATH)/build/bba/bbasm -l build/nextpnr/fpga_interchange/chipdb.bba $(BBA_PATH)
|
||||
|
||||
chipdb: $(BBA_PATH)
|
||||
@ -87,5 +83,11 @@ test: chipdb
|
||||
--package csg324 \
|
||||
--test
|
||||
|
||||
debug_test: chipdb
|
||||
gdb --args $(NEXTPNR_PATH)/build/nextpnr-fpga_interchange \
|
||||
--chipdb $(BBA_PATH) \
|
||||
--package csg324 \
|
||||
--test
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
|
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
|
@ -46,6 +46,24 @@ build/$(DESIGN)_phys.yaml: build/$(DESIGN).phys
|
||||
|
||||
phys_yaml: build/$(DESIGN)_phys.yaml
|
||||
|
||||
verbose: build/$(DESIGN).netlist
|
||||
$(NEXTPNR_BIN) \
|
||||
--chipdb $(BBA_PATH) \
|
||||
--xdc $(DESIGN).xdc \
|
||||
--netlist build/$(DESIGN).netlist \
|
||||
--phys build/$(DESIGN).phys \
|
||||
--package $(PACKAGE) \
|
||||
--verbose
|
||||
|
||||
verbose2: build/$(DESIGN).netlist
|
||||
$(NEXTPNR_BIN) \
|
||||
--chipdb $(BBA_PATH) \
|
||||
--xdc $(DESIGN).xdc \
|
||||
--netlist build/$(DESIGN).netlist \
|
||||
--phys build/$(DESIGN).phys \
|
||||
--package $(PACKAGE) \
|
||||
--debug
|
||||
|
||||
debug: build/$(DESIGN).netlist
|
||||
gdb --args $(NEXTPNR_BIN) \
|
||||
--chipdb $(BBA_PATH) \
|
||||
@ -54,6 +72,15 @@ debug: build/$(DESIGN).netlist
|
||||
--phys build/$(DESIGN).phys \
|
||||
--package $(PACKAGE)
|
||||
|
||||
debug_verbose: build/$(DESIGN).netlist
|
||||
gdb --args $(NEXTPNR_BIN) \
|
||||
--chipdb $(BBA_PATH) \
|
||||
--xdc $(DESIGN).xdc \
|
||||
--netlist build/$(DESIGN).netlist \
|
||||
--phys build/$(DESIGN).phys \
|
||||
--package $(PACKAGE) \
|
||||
--verbose
|
||||
|
||||
build/$(DESIGN).dcp: build/$(DESIGN).netlist build/$(DESIGN).phys $(DESIGN).xdc
|
||||
RAPIDWRIGHT_PATH=$(RAPIDWRIGHT_PATH) \
|
||||
$(RAPIDWRIGHT_PATH)/scripts/invoke_rapidwright.sh \
|
||||
|
@ -1,2 +1,5 @@
|
||||
set_property PACKAGE_PIN N16 [get_ports i]
|
||||
set_property PACKAGE_PIN N15 [get_ports o]
|
||||
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports i]
|
||||
set_property IOSTANDARD LVCMOS33 [get_ports o]
|
||||
|
@ -62,6 +62,10 @@ static PhysicalNetlist::PhysNetlist::RouteBranch::Builder emit_branch(
|
||||
const std::unordered_map<PipId, PlaceStrength> &pip_place_strength,
|
||||
PipId pip,
|
||||
PhysicalNetlist::PhysNetlist::RouteBranch::Builder branch) {
|
||||
if(ctx->is_pip_synthetic(pip)) {
|
||||
log_error("FPGA interchange should not emit synthetic pip %s\n", ctx->nameOfPip(pip));
|
||||
}
|
||||
|
||||
const PipInfoPOD & pip_data = pip_info(ctx->chip_info, pip);
|
||||
const TileTypeInfoPOD & tile_type = loc_info(ctx->chip_info, pip);
|
||||
const TileInstInfoPOD & tile = ctx->chip_info->tiles[pip.tile];
|
||||
@ -107,20 +111,37 @@ static PhysicalNetlist::PhysNetlist::RouteBranch::Builder emit_branch(
|
||||
if(bel_data.category == BEL_CATEGORY_LOGIC) {
|
||||
// This is a psuedo site-pip.
|
||||
auto in_bel_pin = branch.getRouteSegment().initBelPin();
|
||||
IdString src_wire_name = IdString(tile_type.wire_data[pip_data.src_index].name);
|
||||
IdString dst_wire_name = IdString(tile_type.wire_data[pip_data.dst_index].name);
|
||||
WireId src_wire = ctx->getPipSrcWire(pip);
|
||||
WireId dst_wire = ctx->getPipDstWire(pip);
|
||||
|
||||
IdString src_pin;
|
||||
IdString dst_pin;
|
||||
for(IdString pin : ctx->getBelPins(bel)) {
|
||||
if(ctx->getBelPinWire(bel, pin) == src_wire) {
|
||||
NPNR_ASSERT(src_pin == IdString());
|
||||
src_pin = pin;
|
||||
}
|
||||
|
||||
if(ctx->getBelPinWire(bel, pin) == dst_wire) {
|
||||
NPNR_ASSERT(dst_pin == IdString());
|
||||
dst_pin = pin;
|
||||
}
|
||||
}
|
||||
|
||||
NPNR_ASSERT(src_pin != IdString());
|
||||
NPNR_ASSERT(dst_pin != IdString());
|
||||
|
||||
int bel_idx = strings->get_index(bel_name[1].str(ctx));
|
||||
in_bel_pin.setSite(site_idx);
|
||||
in_bel_pin.setBel(bel_idx);
|
||||
in_bel_pin.setPin(strings->get_index(src_wire_name.str(ctx)));
|
||||
in_bel_pin.setPin(strings->get_index(src_pin.str(ctx)));
|
||||
|
||||
auto subbranch = branch.initBranches(1);
|
||||
auto bel_pin_branch = subbranch[0];
|
||||
auto out_bel_pin = bel_pin_branch.getRouteSegment().initBelPin();
|
||||
out_bel_pin.setSite(site_idx);
|
||||
out_bel_pin.setBel(bel_idx);
|
||||
out_bel_pin.setPin(strings->get_index(dst_wire_name.str(ctx)));
|
||||
out_bel_pin.setPin(strings->get_index(dst_pin.str(ctx)));
|
||||
|
||||
return bel_pin_branch;
|
||||
} else if(bel_data.category == BEL_CATEGORY_ROUTING) {
|
||||
@ -185,6 +206,11 @@ static void init_bel_pin(
|
||||
StringEnumerator * strings,
|
||||
const BelPin &bel_pin,
|
||||
PhysicalNetlist::PhysNetlist::RouteBranch::Builder branch) {
|
||||
if(ctx->is_bel_synthetic(bel_pin.bel)) {
|
||||
log_error("FPGA interchange should not emit synthetic BEL pin %s/%s\n",
|
||||
ctx->nameOfBel(bel_pin.bel), bel_pin.pin.c_str(ctx));
|
||||
}
|
||||
|
||||
BelId bel = bel_pin.bel;
|
||||
IdString pin_name = bel_pin.pin;
|
||||
|
||||
@ -248,6 +274,37 @@ static void emit_net(
|
||||
}
|
||||
}
|
||||
}
|
||||
static void find_non_synthetic_edges(const Context * ctx, WireId root_wire,
|
||||
const std::unordered_map<WireId, std::vector<PipId>> &pip_downhill,
|
||||
std::vector<PipId> *root_pips) {
|
||||
std::vector<WireId> wires_to_expand;
|
||||
|
||||
wires_to_expand.push_back(root_wire);
|
||||
while(!wires_to_expand.empty()) {
|
||||
WireId wire = wires_to_expand.back();
|
||||
wires_to_expand.pop_back();
|
||||
|
||||
auto downhill_iter = pip_downhill.find(wire);
|
||||
if(downhill_iter == pip_downhill.end()) {
|
||||
if(root_wire != wire) {
|
||||
log_warning("Wire %s never entered the real fabric?\n",
|
||||
ctx->nameOfWire(wire));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for(PipId pip : pip_downhill.at(wire)) {
|
||||
if(!ctx->is_pip_synthetic(pip)) {
|
||||
// Stop following edges that are non-synthetic, they will be
|
||||
// followed during emit_net
|
||||
root_pips->push_back(pip);
|
||||
} else {
|
||||
// Continue to follow synthetic edges.
|
||||
wires_to_expand.push_back(ctx->getPipDstWire(pip));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::string &filename) {
|
||||
::capnp::MallocMessageBuilder message;
|
||||
@ -272,18 +329,23 @@ void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::str
|
||||
size_t number_placements = 0;
|
||||
for(auto & cell_name : placed_cells) {
|
||||
const CellInfo & cell = *ctx->cells.at(cell_name);
|
||||
if(!ctx->io_port_types.count(cell.type)) {
|
||||
number_placements += 1;
|
||||
|
||||
if(ctx->is_bel_synthetic(cell.bel)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
number_placements += 1;
|
||||
}
|
||||
|
||||
std::vector<IdString> ports;
|
||||
|
||||
std::unordered_map<std::string, std::string> sites;
|
||||
auto placements = phys_netlist.initPlacements(number_placements);
|
||||
auto placement_iter = placements.begin();
|
||||
|
||||
for(auto & cell_name : placed_cells) {
|
||||
const CellInfo & cell = *ctx->cells.at(cell_name);
|
||||
if(ctx->io_port_types.count(cell.type)) {
|
||||
if(ctx->is_bel_synthetic(cell.bel)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -304,7 +366,13 @@ void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::str
|
||||
auto placement = *placement_iter++;
|
||||
|
||||
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));
|
||||
|
||||
size_t bel_index = strings.get_index(bel_name[1].str(ctx));
|
||||
@ -312,43 +380,72 @@ void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::str
|
||||
placement.setIsBelFixed(cell.belStrength >= STRENGTH_FIXED);
|
||||
placement.setIsSiteFixed(cell.belStrength >= STRENGTH_FIXED);
|
||||
|
||||
size_t pin_count = 0;
|
||||
for(const auto & pin : cell.cell_bel_pins) {
|
||||
pin_count += pin.second.size();
|
||||
}
|
||||
if(!ctx->io_port_types.count(cell.type)) {
|
||||
// Don't emit pin map for ports.
|
||||
size_t pin_count = 0;
|
||||
for(const auto & pin : cell.cell_bel_pins) {
|
||||
if(cell.const_ports.count(pin.first)) {
|
||||
continue;
|
||||
}
|
||||
pin_count += pin.second.size();
|
||||
}
|
||||
|
||||
auto pins = placement.initPinMap(pin_count);
|
||||
auto pin_iter = pins.begin();
|
||||
auto pins = placement.initPinMap(pin_count);
|
||||
auto pin_iter = pins.begin();
|
||||
|
||||
for(const auto & cell_to_bel_pins : cell.cell_bel_pins) {
|
||||
std::string cell_pin = cell_to_bel_pins.first.str(ctx);
|
||||
size_t cell_pin_index = strings.get_index(cell_pin);
|
||||
for(const auto & cell_to_bel_pins : cell.cell_bel_pins) {
|
||||
if(cell.const_ports.count(cell_to_bel_pins.first)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for(const auto & bel_pin : cell_to_bel_pins.second) {
|
||||
auto pin_output = *pin_iter++;
|
||||
pin_output.setCellPin(cell_pin_index);
|
||||
pin_output.setBel(bel_index);
|
||||
pin_output.setBelPin(strings.get_index(bel_pin.str(ctx)));
|
||||
std::string cell_pin = cell_to_bel_pins.first.str(ctx);
|
||||
size_t cell_pin_index = strings.get_index(cell_pin);
|
||||
|
||||
for(const auto & bel_pin : cell_to_bel_pins.second) {
|
||||
auto pin_output = *pin_iter++;
|
||||
pin_output.setCellPin(cell_pin_index);
|
||||
pin_output.setBel(bel_index);
|
||||
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 net_iter = nets.begin();
|
||||
for(auto & net_pair : ctx->nets) {
|
||||
auto &net = *net_pair.second;
|
||||
auto net_out = *net_iter++;
|
||||
|
||||
net_out.setName(strings.get_index(net.name.str(ctx)));
|
||||
const CellInfo *driver_cell = net.driver.cell;
|
||||
|
||||
// FIXME: Mark net as signal/vcc/gnd.
|
||||
//
|
||||
// Also vcc/gnd nets needs to get special handling through inverters.
|
||||
// Handle GND and VCC nets.
|
||||
if(driver_cell->bel == ctx->get_gnd_bel()) {
|
||||
IdString gnd_net_name(ctx->chip_info->constants->gnd_net_name);
|
||||
net_out.setName(strings.get_index(gnd_net_name.str(ctx)));
|
||||
net_out.setType(PhysicalNetlist::PhysNetlist::NetType::GND);
|
||||
} else if(driver_cell->bel == ctx->get_vcc_bel()) {
|
||||
IdString vcc_net_name(ctx->chip_info->constants->vcc_net_name);
|
||||
net_out.setName(strings.get_index(vcc_net_name.str(ctx)));
|
||||
net_out.setType(PhysicalNetlist::PhysNetlist::NetType::VCC);
|
||||
} else {
|
||||
net_out.setName(strings.get_index(net.name.str(ctx)));
|
||||
}
|
||||
|
||||
// FIXME: Also vcc/gnd nets needs to get special handling through
|
||||
// inverters.
|
||||
std::unordered_map<WireId, BelPin> root_wires;
|
||||
std::unordered_map<WireId, std::vector<PipId>> pip_downhill;
|
||||
std::unordered_set<PipId> pips;
|
||||
|
||||
const CellInfo *driver_cell = net.driver.cell;
|
||||
if (driver_cell != nullptr && driver_cell->bel != BelId()) {
|
||||
for(IdString bel_pin_name : driver_cell->cell_bel_pins.at(net.driver.port)) {
|
||||
BelPin driver_bel_pin;
|
||||
@ -397,7 +494,27 @@ void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::str
|
||||
}
|
||||
}
|
||||
|
||||
auto sources = net_out.initSources(root_wires.size());
|
||||
std::vector<PipId> root_pips;
|
||||
std::vector<WireId> roots_to_remove;
|
||||
|
||||
for(const auto & root_pair : root_wires) {
|
||||
WireId root_wire = root_pair.first;
|
||||
BelPin src_bel_pin = root_pair.second;
|
||||
|
||||
if(!ctx->is_bel_synthetic(src_bel_pin.bel)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
roots_to_remove.push_back(root_wire);
|
||||
find_non_synthetic_edges(ctx, root_wire, pip_downhill, &root_pips);
|
||||
}
|
||||
|
||||
// Remove wires that have a synthetic root.
|
||||
for(WireId wire : roots_to_remove) {
|
||||
NPNR_ASSERT(root_wires.erase(wire) == 1);
|
||||
}
|
||||
|
||||
auto sources = net_out.initSources(root_wires.size() + root_pips.size());
|
||||
auto source_iter = sources.begin();
|
||||
|
||||
for(const auto & root_pair : root_wires) {
|
||||
@ -410,11 +527,30 @@ void FpgaInterchange::write_physical_netlist(const Context * ctx, const std::str
|
||||
emit_net(ctx, &strings, pip_downhill, sinks, &pips, pip_place_strength, root_wire, source_branch);
|
||||
}
|
||||
|
||||
for(const PipId root : root_pips) {
|
||||
PhysicalNetlist::PhysNetlist::RouteBranch::Builder source_branch = *source_iter++;
|
||||
|
||||
NPNR_ASSERT(pips.erase(root) == 1);
|
||||
WireId root_wire = ctx->getPipDstWire(root);
|
||||
source_branch = emit_branch(ctx, &strings, pip_place_strength, root, source_branch);
|
||||
emit_net(ctx, &strings, pip_downhill, sinks, &pips, pip_place_strength, root_wire, source_branch);
|
||||
}
|
||||
|
||||
// Any pips that were not part of a tree starting from the source are
|
||||
// stubs.
|
||||
auto stubs = net_out.initStubs(pips.size());
|
||||
size_t real_pips = 0;
|
||||
for(PipId pip : pips) {
|
||||
if(ctx->is_pip_synthetic(pip)) {
|
||||
continue;
|
||||
}
|
||||
real_pips += 1;
|
||||
}
|
||||
auto stubs = net_out.initStubs(real_pips);
|
||||
auto stub_iter = stubs.begin();
|
||||
for(PipId pip : pips) {
|
||||
if(ctx->is_pip_synthetic(pip)) {
|
||||
continue;
|
||||
}
|
||||
emit_branch(ctx, &strings, pip_place_strength, pip, *stub_iter++);
|
||||
}
|
||||
}
|
||||
@ -495,6 +631,8 @@ struct ModuleReader {
|
||||
|
||||
ModuleReader(const LogicalNetlistImpl *root,
|
||||
LogicalNetlist::Netlist::CellInstance::Reader cell_inst, bool is_top);
|
||||
|
||||
size_t translate_port_index(LogicalNetlist::Netlist::PortInstance::Reader port_inst) const;
|
||||
};
|
||||
|
||||
struct PortReader {
|
||||
@ -731,8 +869,8 @@ struct LogicalNetlistImpl
|
||||
|
||||
bool is_vector_bit_constant(const std::vector<int32_t> &bits, int i) const
|
||||
{
|
||||
// FIXME: Check if this is right. Assumption is that cells have been
|
||||
// emitted for GND and VCC, e.g. VCC vcc(.P(vcc_net)).
|
||||
// Note: This appears weird, but is correct. This is because VCC/GND
|
||||
// nets are not handled in frontend_base for FPGA interchange.
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -810,11 +948,8 @@ ModuleReader::ModuleReader(const LogicalNetlistImpl *root,
|
||||
PortKey port_key(inst_idx, port_inst.getPort());
|
||||
std::vector<int32_t> & port_connections = connections.at(port_key);
|
||||
|
||||
if(port_inst.getBusIdx().isSingleBit()) {
|
||||
port_connections[0] = net_idx;
|
||||
} else {
|
||||
port_connections.at(port_inst.getBusIdx().getIdx()) = net_idx;
|
||||
}
|
||||
size_t port_idx = translate_port_index(port_inst);
|
||||
port_connections.at(port_idx) = net_idx;
|
||||
}
|
||||
}
|
||||
|
||||
@ -874,5 +1009,19 @@ void FpgaInterchange::read_logical_netlist(Context * ctx, const std::string &fil
|
||||
GenericFrontend<LogicalNetlistImpl>(ctx, netlist_reader, /*split_io=*/false)();
|
||||
}
|
||||
|
||||
size_t ModuleReader::translate_port_index(LogicalNetlist::Netlist::PortInstance::Reader port_inst) const {
|
||||
LogicalNetlist::Netlist::Port::Reader port = root->root.getPortList()[port_inst.getPort()];
|
||||
if(port_inst.getBusIdx().isSingleBit()) {
|
||||
NPNR_ASSERT(port.isBit());
|
||||
return 0;
|
||||
} else {
|
||||
NPNR_ASSERT(port.isBus());
|
||||
uint32_t idx = port_inst.getBusIdx().getIdx();
|
||||
size_t width = get_port_width(port);
|
||||
NPNR_ASSERT(idx >= 0 && idx < width);
|
||||
return width - 1 - idx;
|
||||
}
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
||||
|
@ -70,6 +70,7 @@ void FpgaInterchangeCommandHandler::customBitstream(Context *ctx)
|
||||
std::unique_ptr<Context> FpgaInterchangeCommandHandler::createContext(std::unordered_map<std::string, Property> &values)
|
||||
{
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
ArchArgs chipArgs;
|
||||
if (!vm.count("chipdb")) {
|
||||
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));
|
||||
|
||||
if (vm.count("verbose")) {
|
||||
ctx->verbose = true;
|
||||
}
|
||||
if (vm.count("debug")) {
|
||||
ctx->verbose = true;
|
||||
ctx->debug = true;
|
||||
}
|
||||
|
||||
ctx->init();
|
||||
|
||||
if (vm.count("netlist")) {
|
||||
ctx->read_logical_netlist(vm["netlist"].as<std::string>());
|
||||
}
|
||||
|
@ -22,9 +22,9 @@
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
bool verbose_site_router(const Context *ctx) { return ctx->verbose; }
|
||||
bool verbose_site_router(const Context *ctx) { return ctx->debug; }
|
||||
|
||||
void Arch::SiteRouter::bindBel(CellInfo *cell)
|
||||
void SiteRouter::bindBel(CellInfo *cell)
|
||||
{
|
||||
auto result = cells_in_site.emplace(cell);
|
||||
NPNR_ASSERT(result.second);
|
||||
@ -32,7 +32,7 @@ void Arch::SiteRouter::bindBel(CellInfo *cell)
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Arch::SiteRouter::unbindBel(CellInfo *cell)
|
||||
void SiteRouter::unbindBel(CellInfo *cell)
|
||||
{
|
||||
NPNR_ASSERT(cells_in_site.erase(cell) == 1);
|
||||
|
||||
@ -56,6 +56,22 @@ struct RouteNode
|
||||
|
||||
PipId pip; // What pip was taken to reach this node.
|
||||
WireId wire; // What wire is this routing node located at?
|
||||
|
||||
void print_route(const Context *ctx) const
|
||||
{
|
||||
log_info(" %s (via %s)\n", ctx->nameOfWire(wire), ctx->nameOfPip(pip));
|
||||
|
||||
Node node = parent;
|
||||
while (node != RouteNode::Node()) {
|
||||
if (node->pip != PipId()) {
|
||||
log_info(" %s (via %s)\n", ctx->nameOfWire(node->wire), ctx->nameOfPip(node->pip));
|
||||
} else {
|
||||
log_info(" %s\n", ctx->nameOfWire(node->wire));
|
||||
}
|
||||
|
||||
node = node->parent;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct RouteNodeStorage
|
||||
@ -260,6 +276,11 @@ struct SiteInformation
|
||||
if (!result.second && result.first->second != net) {
|
||||
// Conflict, this wire is already in use and it's not
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -303,6 +324,8 @@ struct SiteInformation
|
||||
std::unordered_set<const NetInfo *> nets_fully_within_site;
|
||||
|
||||
bool is_net_within_site(const NetInfo *net) const { return nets_fully_within_site.count(net); }
|
||||
|
||||
void print_current_state() const;
|
||||
};
|
||||
|
||||
struct SiteExpansionLoop
|
||||
@ -599,6 +622,10 @@ bool route_site(const Context *ctx, SiteInformation *site_info)
|
||||
|
||||
std::unordered_map<WireId, std::unordered_set<const NetInfo *>> wire_congestion;
|
||||
|
||||
for (auto &consumed_wire : site_info->consumed_wires) {
|
||||
wire_congestion[consumed_wire.first].emplace(consumed_wire.second);
|
||||
}
|
||||
|
||||
for (auto &expansion_wire : wire_to_expansion) {
|
||||
auto &expansion = *expansion_wire.second;
|
||||
|
||||
@ -636,8 +663,15 @@ bool route_site(const Context *ctx, SiteInformation *site_info)
|
||||
|
||||
if (uncongestion_route != RouteNode::Node()) {
|
||||
// Select a trivially uncongested route if possible.
|
||||
NPNR_ASSERT(site_info->select_route(expansion.first_wire, uncongestion_route, expansion.net_for_wire,
|
||||
&newly_consumed_wires));
|
||||
if (!site_info->select_route(expansion.first_wire, uncongestion_route, expansion.net_for_wire,
|
||||
&newly_consumed_wires)) {
|
||||
log_info("Failed to bind uncongested path with wire %s on net %s\n",
|
||||
ctx->nameOfWire(expansion.first_wire), expansion.net_for_wire->name.c_str(ctx));
|
||||
uncongestion_route->print_route(ctx);
|
||||
|
||||
site_info->print_current_state();
|
||||
NPNR_ASSERT(false);
|
||||
}
|
||||
completed_wires.push_back(expansion.first_wire);
|
||||
}
|
||||
}
|
||||
@ -670,7 +704,7 @@ bool route_site(const Context *ctx, SiteInformation *site_info)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Arch::SiteRouter::checkSiteRouting(const Context *ctx, const Arch::TileStatus &tile_status) const
|
||||
bool SiteRouter::checkSiteRouting(const Context *ctx, const TileStatus &tile_status) const
|
||||
{
|
||||
if (!dirty) {
|
||||
return site_ok;
|
||||
@ -747,4 +781,42 @@ bool Arch::SiteRouter::checkSiteRouting(const Context *ctx, const Arch::TileStat
|
||||
return site_ok;
|
||||
}
|
||||
|
||||
void SiteInformation::print_current_state() const
|
||||
{
|
||||
const CellInfo *cell = *cells_in_site.begin();
|
||||
BelId bel = cell->bel;
|
||||
const auto &bel_data = bel_info(ctx->chip_info, bel);
|
||||
const auto &site_inst = site_inst_info(ctx->chip_info, bel.tile, bel_data.site);
|
||||
|
||||
log_info("Site %s\n", site_inst.name.get());
|
||||
|
||||
log_info(" Cells in site:\n");
|
||||
for (CellInfo *cell : cells_in_site) {
|
||||
log_info(" - %s (%s)\n", cell->name.c_str(ctx), cell->type.c_str(ctx));
|
||||
}
|
||||
|
||||
log_info(" Nets in site:\n");
|
||||
for (auto *net : nets_in_site) {
|
||||
log_info(" - %s, pins in site:\n", net->name.c_str(ctx));
|
||||
if (net->driver.cell && cells_in_site.count(net->driver.cell)) {
|
||||
log_info(" - %s/%s (%s)\n", net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx),
|
||||
net->driver.cell->type.c_str(ctx));
|
||||
}
|
||||
|
||||
for (const auto user : net->users) {
|
||||
if (user.cell && cells_in_site.count(user.cell)) {
|
||||
log_info(" - %s/%s (%s)\n", user.cell->name.c_str(ctx), user.port.c_str(ctx),
|
||||
user.cell->type.c_str(ctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log_info(" Consumed wires:\n");
|
||||
for (auto consumed_wire : consumed_wires) {
|
||||
WireId wire = consumed_wire.first;
|
||||
const NetInfo *net = consumed_wire.second;
|
||||
log_info(" - %s is bound to %s\n", ctx->nameOfWire(wire), net->name.c_str(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
45
fpga_interchange/site_router.h
Normal file
45
fpga_interchange/site_router.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 "site_router.h" via "nextpnr.h" only.
|
||||
#endif
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
struct Context;
|
||||
struct TileStatus;
|
||||
|
||||
struct SiteRouter
|
||||
{
|
||||
SiteRouter(int16_t site) : site(site), dirty(false), site_ok(true) {}
|
||||
|
||||
std::unordered_set<CellInfo *> cells_in_site;
|
||||
const int16_t site;
|
||||
|
||||
mutable bool dirty;
|
||||
mutable bool site_ok;
|
||||
|
||||
void bindBel(CellInfo *cell);
|
||||
void unbindBel(CellInfo *cell);
|
||||
bool checkSiteRouting(const Context *ctx, const TileStatus &tile_status) const;
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
@ -18,6 +18,11 @@ pin connectivity tests. Example test_data.yaml:
|
||||
pip_test:
|
||||
- src_wire: CLBLM_R_X11Y93/CLBLM_L_D3
|
||||
dst_wire: SLICE_X15Y93.SLICEL/D3
|
||||
pip_chain_test:
|
||||
- wires:
|
||||
- $CONSTANTS_X0Y0.$CONSTANTS/$GND_SOURCE
|
||||
- $CONSTANTS_X0Y0/$GND_NODE
|
||||
- TIEOFF_X3Y145.TIEOFF/$GND_SITE_WIRE
|
||||
bel_pin_test:
|
||||
- bel: SLICE_X15Y93.SLICEL/D6LUT
|
||||
pin: A3
|
||||
@ -25,25 +30,48 @@ bel_pin_test:
|
||||
|
||||
"""
|
||||
import yaml
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
|
||||
def check_arch_api(ctx):
|
||||
success = True
|
||||
pips_tested = 0
|
||||
pips_failed = 0
|
||||
|
||||
def test_pip(src_wire_name, dst_wire_name):
|
||||
nonlocal success
|
||||
nonlocal pips_tested
|
||||
nonlocal pips_failed
|
||||
|
||||
pip = None
|
||||
for pip_name in ctx.getPipsDownhill(src_wire_name):
|
||||
if ctx.getPipDstWire(pip_name) == dst_wire_name:
|
||||
pip = pip_name
|
||||
src_wire = ctx.getPipSrcWire(pip_name)
|
||||
assert src_wire == src_wire_name, (
|
||||
src_wire, src_wire_name)
|
||||
|
||||
|
||||
if pip is None:
|
||||
success = False
|
||||
pips_failed += 1
|
||||
print('Pip from {} to {} failed'.format(src_wire_name, dst_wire_name))
|
||||
else:
|
||||
pips_tested += 1
|
||||
bel_pins_tested = 0
|
||||
with open('test_data.yaml', 'r') as f:
|
||||
test_data = yaml.safe_load(f.read())
|
||||
if 'pip_test' in test_data:
|
||||
for pip_test in test_data['pip_test']:
|
||||
pip = None
|
||||
for pip_name in ctx.getPipsDownhill(pip_test['src_wire']):
|
||||
if ctx.getPipDstWire(pip_name) == pip_test['dst_wire']:
|
||||
pip = pip_name
|
||||
src_wire = ctx.getPipSrcWire(pip_name)
|
||||
assert src_wire == pip_test['src_wire'], (
|
||||
src_wire, pip_test['src_wire'])
|
||||
test_pip(pip_test['src_wire'], pip_test['dst_wire'])
|
||||
|
||||
assert pip is not None
|
||||
pips_tested += 1
|
||||
if 'pip_chain_test' in test_data:
|
||||
for chain_test in test_data['pip_chain_test']:
|
||||
wires = chain_test['wires']
|
||||
for src_wire, dst_wire in zip(wires, wires[1:]):
|
||||
test_pip(src_wire, dst_wire)
|
||||
|
||||
if 'bel_pin_test' in test_data:
|
||||
for bel_pin_test in test_data['bel_pin_test']:
|
||||
@ -54,4 +82,9 @@ def check_arch_api(ctx):
|
||||
|
||||
print('Tested {} pips and {} bel pins'.format(pips_tested, bel_pins_tested))
|
||||
|
||||
if not success:
|
||||
print('{} pips failed'.format(pips_failed))
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
check_arch_api(ctx)
|
||||
|
Loading…
Reference in New Issue
Block a user