nextpnr/ecp5/arch.cc
D. Shah 6d23461bcd ecp5: Proof-of-concept using IdStringList for bel names
This uses the new IdStringList API to store bel names for the ECP5. Note
that other arches and the GUI do not yet build with this
proof-of-concept patch.

getBelByName still uses the old implementation and could be more
efficiently implemented with further development.

Signed-off-by: D. Shah <dave@ds0.me>
2021-02-02 17:00:12 +00:00

1293 lines
46 KiB
C++

/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
*
* 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 <algorithm>
#include <boost/iostreams/device/mapped_file.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <cmath>
#include <cstring>
#include "embed.h"
#include "gfx.h"
#include "globals.h"
#include "log.h"
#include "nextpnr.h"
#include "placer1.h"
#include "placer_heap.h"
#include "router1.h"
#include "router2.h"
#include "timing.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
static std::tuple<int, int, std::string> split_identifier_name(const std::string &name)
{
size_t first_slash = name.find('/');
NPNR_ASSERT(first_slash != std::string::npos);
size_t second_slash = name.find('/', first_slash + 1);
NPNR_ASSERT(second_slash != std::string::npos);
return std::make_tuple(std::stoi(name.substr(1, first_slash)),
std::stoi(name.substr(first_slash + 2, second_slash - first_slash)),
name.substr(second_slash + 1));
};
// -----------------------------------------------------------------------
void IdString::initialize_arch(const BaseCtx *ctx)
{
#define X(t) initialize_add(ctx, #t, ID_##t);
#include "constids.inc"
#undef X
}
// -----------------------------------------------------------------------
static const ChipInfoPOD *get_chip_info(ArchArgs::ArchArgsTypes chip)
{
std::string chipdb;
if (chip == ArchArgs::LFE5U_12F || chip == ArchArgs::LFE5U_25F || chip == ArchArgs::LFE5UM_25F ||
chip == ArchArgs::LFE5UM5G_25F) {
chipdb = "ecp5/chipdb-25k.bin";
} else if (chip == ArchArgs::LFE5U_45F || chip == ArchArgs::LFE5UM_45F || chip == ArchArgs::LFE5UM5G_45F) {
chipdb = "ecp5/chipdb-45k.bin";
} else if (chip == ArchArgs::LFE5U_85F || chip == ArchArgs::LFE5UM_85F || chip == ArchArgs::LFE5UM5G_85F) {
chipdb = "ecp5/chipdb-85k.bin";
} else {
log_error("Unknown chip\n");
}
auto ptr = reinterpret_cast<const RelPtr<ChipInfoPOD> *>(get_chipdb(chipdb));
if (ptr == nullptr)
return nullptr;
return ptr->get();
}
bool Arch::isAvailable(ArchArgs::ArchArgsTypes chip) { return get_chip_info(chip) != nullptr; }
std::vector<std::string> Arch::getSupportedPackages(ArchArgs::ArchArgsTypes chip)
{
const ChipInfoPOD *chip_info = get_chip_info(chip);
std::vector<std::string> packages;
for (auto &pkg : chip_info->package_info)
packages.push_back(pkg.name.get());
return packages;
}
// -----------------------------------------------------------------------
Arch::Arch(ArchArgs args) : args(args)
{
chip_info = get_chip_info(args.type);
if (chip_info == nullptr)
log_error("Unsupported ECP5 chip type.\n");
if (chip_info->const_id_count != DB_CONST_ID_COUNT)
log_error("Chip database 'bba' and nextpnr code are out of sync; please rebuild (or contact distribution "
"maintainer)!\n");
package_info = nullptr;
for (auto &pkg : chip_info->package_info) {
if (args.package == pkg.name.get()) {
package_info = &pkg;
break;
}
}
speed_grade = &(chip_info->speed_grades[args.speed]);
if (!package_info)
log_error("Unsupported package '%s' for '%s'.\n", args.package.c_str(), getChipName().c_str());
bel_to_cell.resize(chip_info->height * chip_info->width * max_loc_bels, nullptr);
std::unordered_set<IdString> bel_types;
for (BelId bel : getBels()) {
bel_types.insert(getBelType(bel));
}
for (IdString bel_type : bel_types) {
cell_types.push_back(bel_type);
BelBucketId bucket;
bucket.name = bel_type;
buckets.push_back(bucket);
}
for (int i = 0; i < chip_info->width; i++)
x_ids.push_back(id(stringf("X%d", i)));
for (int i = 0; i < chip_info->height; i++)
y_ids.push_back(id(stringf("Y%d", i)));
}
// -----------------------------------------------------------------------
std::string Arch::getChipName() const
{
if (args.type == ArchArgs::LFE5U_12F) {
return "LFE5U-12F";
} else if (args.type == ArchArgs::LFE5U_25F) {
return "LFE5U-25F";
} else if (args.type == ArchArgs::LFE5U_45F) {
return "LFE5U-45F";
} else if (args.type == ArchArgs::LFE5U_85F) {
return "LFE5U-85F";
} else if (args.type == ArchArgs::LFE5UM_25F) {
return "LFE5UM-25F";
} else if (args.type == ArchArgs::LFE5UM_45F) {
return "LFE5UM-45F";
} else if (args.type == ArchArgs::LFE5UM_85F) {
return "LFE5UM-85F";
} else if (args.type == ArchArgs::LFE5UM5G_25F) {
return "LFE5UM5G-25F";
} else if (args.type == ArchArgs::LFE5UM5G_45F) {
return "LFE5UM5G-45F";
} else if (args.type == ArchArgs::LFE5UM5G_85F) {
return "LFE5UM5G-85F";
} else {
log_error("Unknown chip\n");
}
}
std::string Arch::getFullChipName() const
{
std::string name = getChipName();
name += "-";
switch (args.speed) {
case ArchArgs::SPEED_6:
name += "6";
break;
case ArchArgs::SPEED_7:
name += "7";
break;
case ArchArgs::SPEED_8:
case ArchArgs::SPEED_8_5G:
name += "8";
break;
}
name += args.package;
return name;
}
// -----------------------------------------------------------------------
IdString Arch::archArgsToId(ArchArgs args) const
{
if (args.type == ArchArgs::LFE5U_12F)
return id("lfe5u_12f");
if (args.type == ArchArgs::LFE5U_25F)
return id("lfe5u_25f");
if (args.type == ArchArgs::LFE5U_45F)
return id("lfe5u_45f");
if (args.type == ArchArgs::LFE5U_85F)
return id("lfe5u_85f");
if (args.type == ArchArgs::LFE5UM_25F)
return id("lfe5um_25f");
if (args.type == ArchArgs::LFE5UM_45F)
return id("lfe5um_45f");
if (args.type == ArchArgs::LFE5UM_85F)
return id("lfe5um_85f");
if (args.type == ArchArgs::LFE5UM5G_25F)
return id("lfe5um5g_25f");
if (args.type == ArchArgs::LFE5UM5G_45F)
return id("lfe5um5g_45f");
if (args.type == ArchArgs::LFE5UM5G_85F)
return id("lfe5um5g_85f");
return IdString();
}
// -----------------------------------------------------------------------
BelId Arch::getBelByName(IdStringList name) const
{
// TODO: take advantage of IdStringList for fast parsing
BelId ret;
#if 0
auto it = bel_by_name.find(name);
if (it != bel_by_name.end())
return it->second;
#endif
Location loc;
std::string basename;
std::tie(loc.x, loc.y, basename) = split_identifier_name(name.str(getCtx()));
ret.location = loc;
const LocationTypePOD *loci = locInfo(ret);
for (int i = 0; i < int(loci->bel_data.size()); i++) {
if (std::strcmp(loci->bel_data[i].name.get(), basename.c_str()) == 0) {
ret.index = i;
break;
}
}
#if 0
if (ret.index >= 0)
bel_by_name[name] = ret;
#endif
return ret;
}
BelRange Arch::getBelsByTile(int x, int y) const
{
BelRange br;
br.b.cursor_tile = y * chip_info->width + x;
br.e.cursor_tile = y * chip_info->width + x;
br.b.cursor_index = 0;
br.e.cursor_index = int(chip_info->locations[chip_info->location_type[br.b.cursor_tile]].bel_data.size()) - 1;
br.b.chip = chip_info;
br.e.chip = chip_info;
if (br.e.cursor_index == -1)
++br.e.cursor_index;
else
++br.e;
return br;
}
WireId Arch::getBelPinWire(BelId bel, IdString pin) const
{
WireId ret;
NPNR_ASSERT(bel != BelId());
for (auto &bw : locInfo(bel)->bel_data[bel.index].bel_wires)
if (bw.port == pin.index) {
ret.location = bel.location + bw.rel_wire_loc;
ret.index = bw.wire_index;
break;
}
return ret;
}
PortType Arch::getBelPinType(BelId bel, IdString pin) const
{
NPNR_ASSERT(bel != BelId());
for (auto &bw : locInfo(bel)->bel_data[bel.index].bel_wires)
if (bw.port == pin.index)
return PortType(bw.type);
return PORT_INOUT;
}
// -----------------------------------------------------------------------
WireId Arch::getWireByName(IdString name) const
{
WireId ret;
auto it = wire_by_name.find(name);
if (it != wire_by_name.end())
return it->second;
Location loc;
std::string basename;
std::tie(loc.x, loc.y, basename) = split_identifier_name(name.str(this));
ret.location = loc;
const LocationTypePOD *loci = locInfo(ret);
for (int i = 0; i < int(loci->wire_data.size()); i++) {
if (std::strcmp(loci->wire_data[i].name.get(), basename.c_str()) == 0) {
ret.index = i;
ret.location = loc;
break;
}
}
if (ret.index >= 0)
wire_by_name[name] = ret;
else
ret.location = Location();
return ret;
}
// -----------------------------------------------------------------------
PipId Arch::getPipByName(IdString name) const
{
auto it = pip_by_name.find(name);
if (it != pip_by_name.end())
return it->second;
PipId ret;
Location loc;
std::string basename;
std::tie(loc.x, loc.y, basename) = split_identifier_name(name.str(this));
ret.location = loc;
const LocationTypePOD *loci = locInfo(ret);
for (int i = 0; i < int(loci->pip_data.size()); i++) {
PipId curr;
curr.location = loc;
curr.index = i;
pip_by_name[getPipName(curr)] = curr;
}
if (pip_by_name.find(name) == pip_by_name.end())
NPNR_ASSERT_FALSE_STR("no pip named " + name.str(this));
return pip_by_name[name];
}
IdString Arch::getPipName(PipId pip) const
{
NPNR_ASSERT(pip != PipId());
int x = pip.location.x;
int y = pip.location.y;
std::string src_name = getWireName(getPipSrcWire(pip)).str(this);
std::replace(src_name.begin(), src_name.end(), '/', '.');
std::string dst_name = getWireName(getPipDstWire(pip)).str(this);
std::replace(dst_name.begin(), dst_name.end(), '/', '.');
return id("X" + std::to_string(x) + "/Y" + std::to_string(y) + "/" + src_name + ".->." + dst_name);
}
// -----------------------------------------------------------------------
BelId Arch::getPackagePinBel(const std::string &pin) const
{
for (auto &ppin : package_info->pin_data) {
if (ppin.name.get() == pin) {
BelId bel;
bel.location = ppin.abs_loc;
bel.index = ppin.bel_index;
return bel;
}
}
return BelId();
}
std::string Arch::getBelPackagePin(BelId bel) const
{
for (auto &ppin : package_info->pin_data) {
if (Location(ppin.abs_loc) == bel.location && ppin.bel_index == bel.index) {
return ppin.name.get();
}
}
return "";
}
int Arch::getPioBelBank(BelId bel) const
{
for (auto &pio : chip_info->pio_info) {
if (Location(pio.abs_loc) == bel.location && pio.bel_index == bel.index) {
return pio.bank;
}
}
NPNR_ASSERT_FALSE("failed to find PIO");
}
std::string Arch::getPioFunctionName(BelId bel) const
{
for (auto &pio : chip_info->pio_info) {
if (Location(pio.abs_loc) == bel.location && pio.bel_index == bel.index) {
const char *func = pio.function_name.get();
if (func == nullptr)
return "";
else
return func;
}
}
NPNR_ASSERT_FALSE("failed to find PIO");
}
BelId Arch::getPioByFunctionName(const std::string &name) const
{
for (auto &pio : chip_info->pio_info) {
const char *func = pio.function_name.get();
if (func != nullptr && func == name) {
BelId bel;
bel.location = pio.abs_loc;
bel.index = pio.bel_index;
return bel;
}
}
return BelId();
}
std::vector<IdString> Arch::getBelPins(BelId bel) const
{
std::vector<IdString> ret;
NPNR_ASSERT(bel != BelId());
for (auto &bw : locInfo(bel)->bel_data[bel.index].bel_wires) {
IdString id;
id.index = bw.port;
ret.push_back(id);
}
return ret;
}
BelId Arch::getBelByLocation(Loc loc) const
{
if (loc.x >= chip_info->width || loc.y >= chip_info->height)
return BelId();
const LocationTypePOD &locI = chip_info->locations[chip_info->location_type[loc.y * chip_info->width + loc.x]];
for (int i = 0; i < int(locI.bel_data.size()); i++) {
if (locI.bel_data[i].z == loc.z) {
BelId bi;
bi.location.x = loc.x;
bi.location.y = loc.y;
bi.index = i;
return bi;
}
}
return BelId();
}
// -----------------------------------------------------------------------
delay_t Arch::estimateDelay(WireId src, WireId dst) const
{
int num_uh = locInfo(dst)->wire_data[dst.index].pips_uphill.size();
if (num_uh < 6) {
for (auto uh : getPipsUphill(dst)) {
if (getPipSrcWire(uh) == src)
return getPipDelay(uh).maxDelay();
}
}
auto est_location = [&](WireId w) -> std::pair<int, int> {
const auto &wire = locInfo(w)->wire_data[w.index];
if (w == gsrclk_wire) {
auto phys_wire = getPipSrcWire(*(getPipsUphill(w).begin()));
return std::make_pair(int(phys_wire.location.x), int(phys_wire.location.y));
} else if (wire.bel_pins.size() > 0) {
return std::make_pair(w.location.x + wire.bel_pins[0].rel_bel_loc.x,
w.location.y + wire.bel_pins[0].rel_bel_loc.y);
} else if (wire.pips_downhill.size() > 0) {
return std::make_pair(w.location.x + wire.pips_downhill[0].rel_loc.x,
w.location.y + wire.pips_downhill[0].rel_loc.y);
} else if (wire.pips_uphill.size() > 0) {
return std::make_pair(w.location.x + wire.pips_uphill[0].rel_loc.x,
w.location.y + wire.pips_uphill[0].rel_loc.y);
} else {
return std::make_pair(int(w.location.x), int(w.location.y));
}
};
auto src_loc = est_location(src);
std::pair<int, int> dst_loc;
if (wire_loc_overrides.count(dst)) {
dst_loc = wire_loc_overrides.at(dst);
} else {
dst_loc = est_location(dst);
}
int dx = abs(src_loc.first - dst_loc.first), dy = abs(src_loc.second - dst_loc.second);
return (120 - 22 * args.speed) *
(6 + std::max(dx - 5, 0) + std::max(dy - 5, 0) + 2 * (std::min(dx, 5) + std::min(dy, 5)));
}
ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const
{
ArcBounds bb;
bb.x0 = src.location.x;
bb.y0 = src.location.y;
bb.x1 = src.location.x;
bb.y1 = src.location.y;
auto extend = [&](int x, int y) {
bb.x0 = std::min(bb.x0, x);
bb.x1 = std::max(bb.x1, x);
bb.y0 = std::min(bb.y0, y);
bb.y1 = std::max(bb.y1, y);
};
auto est_location = [&](WireId w) -> std::pair<int, int> {
const auto &wire = locInfo(w)->wire_data[w.index];
if (w == gsrclk_wire) {
auto phys_wire = getPipSrcWire(*(getPipsUphill(w).begin()));
return std::make_pair(int(phys_wire.location.x), int(phys_wire.location.y));
} else if (wire.bel_pins.size() > 0) {
return std::make_pair(w.location.x + wire.bel_pins[0].rel_bel_loc.x,
w.location.y + wire.bel_pins[0].rel_bel_loc.y);
} else if (wire.pips_downhill.size() > 0) {
return std::make_pair(w.location.x + wire.pips_downhill[0].rel_loc.x,
w.location.y + wire.pips_downhill[0].rel_loc.y);
} else if (wire.pips_uphill.size() > 0) {
return std::make_pair(w.location.x + wire.pips_uphill[0].rel_loc.x,
w.location.y + wire.pips_uphill[0].rel_loc.y);
} else {
return std::make_pair(int(w.location.x), int(w.location.y));
}
};
auto src_loc = est_location(src);
extend(src_loc.first, src_loc.second);
if (wire_loc_overrides.count(src)) {
extend(wire_loc_overrides.at(src).first, wire_loc_overrides.at(src).second);
}
std::pair<int, int> dst_loc;
extend(dst.location.x, dst.location.y);
if (wire_loc_overrides.count(dst)) {
dst_loc = wire_loc_overrides.at(dst);
} else {
dst_loc = est_location(dst);
}
extend(dst_loc.first, dst_loc.second);
return bb;
}
delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
{
const auto &driver = net_info->driver;
if ((driver.port == id_FCO && sink.port == id_FCI) || sink.port == id_FXA || sink.port == id_FXB)
return 0;
auto driver_loc = getBelLocation(driver.cell->bel);
auto sink_loc = getBelLocation(sink.cell->bel);
// Encourage use of direct interconnect
if (driver_loc.x == sink_loc.x && driver_loc.y == sink_loc.y) {
if ((sink.port == id_A0 || sink.port == id_A1) && (driver.port == id_F1) &&
(driver_loc.z == 2 || driver_loc.z == 3))
return 0;
if ((sink.port == id_B0 || sink.port == id_B1) && (driver.port == id_F1) &&
(driver_loc.z == 0 || driver_loc.z == 1))
return 0;
if ((sink.port == id_C0 || sink.port == id_C1) && (driver.port == id_F0) &&
(driver_loc.z == 2 || driver_loc.z == 3))
return 0;
if ((sink.port == id_D0 || sink.port == id_D1) && (driver.port == id_F0) &&
(driver_loc.z == 0 || driver_loc.z == 1))
return 0;
}
int dx = abs(driver_loc.x - sink_loc.x), dy = abs(driver_loc.y - sink_loc.y);
return (120 - 22 * args.speed) *
(6 + std::max(dx - 5, 0) + std::max(dy - 5, 0) + 2 * (std::min(dx, 5) + std::min(dy, 5)));
}
bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const
{
if (net_info->driver.port == id_FCO && sink.port == id_FCI) {
budget = 0;
return true;
} else if (sink.port == id_FXA || sink.port == id_FXB) {
budget = 0;
return true;
} else {
return false;
}
}
delay_t Arch::getRipupDelayPenalty() const { return 400; }
// -----------------------------------------------------------------------
bool Arch::place()
{
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
if (placer == "heap") {
PlacerHeapCfg cfg(getCtx());
cfg.criticalityExponent = 4;
cfg.ioBufTypes.insert(id_TRELLIS_IO);
if (!placer_heap(getCtx(), cfg))
return false;
} else if (placer == "sa") {
if (!placer1(getCtx(), Placer1Cfg(getCtx())))
return false;
} else {
log_error("ECP5 architecture does not support placer '%s'\n", placer.c_str());
}
permute_luts();
// In out-of-context mode, create a locked macro
if (bool_or_default(settings, id("arch.ooc")))
for (auto &cell : cells)
cell.second->belStrength = STRENGTH_LOCKED;
getCtx()->settings[getCtx()->id("place")] = 1;
archInfoToAttributes();
return true;
}
bool Arch::route()
{
std::string router = str_or_default(settings, id("router"), defaultRouter);
setupWireLocations();
route_ecp5_globals(getCtx());
assignArchInfo();
assign_budget(getCtx(), true);
bool result;
if (router == "router1") {
result = router1(getCtx(), Router1Cfg(getCtx()));
} else if (router == "router2") {
router2(getCtx(), Router2Cfg(getCtx()));
result = true;
} else {
log_error("ECP5 architecture does not support router '%s'\n", router.c_str());
}
getCtx()->settings[getCtx()->id("route")] = 1;
archInfoToAttributes();
return result;
}
// -----------------------------------------------------------------------
std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
{
std::vector<GraphicElement> ret;
if (decal.type == DecalId::TYPE_GROUP) {
int type = decal.z;
int x = decal.location.x;
int y = decal.location.y;
if (type == GroupId::TYPE_SWITCHBOX) {
GraphicElement el;
el.type = GraphicElement::TYPE_BOX;
el.style = GraphicElement::STYLE_FRAME;
el.x1 = x + switchbox_x1;
el.x2 = x + switchbox_x2;
el.y1 = y + switchbox_y1;
el.y2 = y + switchbox_y2;
ret.push_back(el);
}
} else if (decal.type == DecalId::TYPE_WIRE) {
WireId wire;
wire.index = decal.z;
wire.location = decal.location;
auto wire_type = getWireType(wire);
int x = decal.location.x;
int y = decal.location.y;
GraphicElement::style_t style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
GfxTileWireId tilewire = GfxTileWireId(locInfo(wire)->wire_data[wire.index].tile_wire);
gfxTileWire(ret, x, y, chip_info->width, chip_info->height, wire_type, tilewire, style);
} else if (decal.type == DecalId::TYPE_PIP) {
PipId pip;
pip.index = decal.z;
pip.location = decal.location;
WireId src_wire = getPipSrcWire(pip);
WireId dst_wire = getPipDstWire(pip);
int x = decal.location.x;
int y = decal.location.y;
GfxTileWireId src_id = GfxTileWireId(locInfo(src_wire)->wire_data[src_wire.index].tile_wire);
GfxTileWireId dst_id = GfxTileWireId(locInfo(dst_wire)->wire_data[dst_wire.index].tile_wire);
GraphicElement::style_t style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_HIDDEN;
gfxTilePip(ret, x, y, chip_info->width, chip_info->height, src_wire, getWireType(src_wire), src_id, dst_wire,
getWireType(dst_wire), dst_id, style);
} else if (decal.type == DecalId::TYPE_BEL) {
BelId bel;
bel.index = decal.z;
bel.location = decal.location;
auto bel_type = getBelType(bel);
int x = decal.location.x;
int y = decal.location.y;
int z = locInfo(bel)->bel_data[bel.index].z;
GraphicElement::style_t style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
gfxTileBel(ret, x, y, z, chip_info->width, chip_info->height, bel_type, style);
}
return ret;
}
DecalXY Arch::getBelDecal(BelId bel) const
{
DecalXY decalxy;
decalxy.decal.type = DecalId::TYPE_BEL;
decalxy.decal.location = bel.location;
decalxy.decal.z = bel.index;
decalxy.decal.active = (bel_to_cell.at(getBelFlatIndex(bel)) != nullptr);
return decalxy;
}
DecalXY Arch::getWireDecal(WireId wire) const
{
DecalXY decalxy;
decalxy.decal.type = DecalId::TYPE_WIRE;
decalxy.decal.location = wire.location;
decalxy.decal.z = wire.index;
decalxy.decal.active = getBoundWireNet(wire) != nullptr;
return decalxy;
}
DecalXY Arch::getPipDecal(PipId pip) const
{
DecalXY decalxy;
decalxy.decal.type = DecalId::TYPE_PIP;
decalxy.decal.location = pip.location;
decalxy.decal.z = pip.index;
decalxy.decal.active = getBoundPipNet(pip) != nullptr;
return decalxy;
};
DecalXY Arch::getGroupDecal(GroupId group) const
{
DecalXY decalxy;
decalxy.decal.type = DecalId::TYPE_GROUP;
decalxy.decal.location = group.location;
decalxy.decal.z = group.type;
decalxy.decal.active = true;
return decalxy;
}
// -----------------------------------------------------------------------
bool Arch::getDelayFromTimingDatabase(IdString tctype, IdString from, IdString to, DelayInfo &delay) const
{
auto fnd_dk = celldelay_cache.find({tctype, from, to});
if (fnd_dk != celldelay_cache.end()) {
delay = fnd_dk->second.second;
return fnd_dk->second.first;
}
for (auto &tc : speed_grade->cell_timings) {
if (tc.cell_type == tctype.index) {
for (auto &dly : tc.prop_delays) {
if (dly.from_port == from.index && dly.to_port == to.index) {
delay.max_delay = dly.max_delay;
delay.min_delay = dly.min_delay;
celldelay_cache[{tctype, from, to}] = std::make_pair(true, delay);
return true;
}
}
celldelay_cache[{tctype, from, to}] = std::make_pair(false, DelayInfo());
return false;
}
}
NPNR_ASSERT_FALSE("failed to find timing cell in db");
}
void Arch::getSetupHoldFromTimingDatabase(IdString tctype, IdString clock, IdString port, DelayInfo &setup,
DelayInfo &hold) const
{
for (auto &tc : speed_grade->cell_timings) {
if (tc.cell_type == tctype.index) {
for (auto &sh : tc.setup_holds) {
if (sh.clock_port == clock.index && sh.sig_port == port.index) {
setup.max_delay = sh.max_setup;
setup.min_delay = sh.min_setup;
hold.max_delay = sh.max_hold;
hold.min_delay = sh.min_hold;
return;
}
}
}
}
NPNR_ASSERT_FALSE("failed to find timing cell in db");
}
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const
{
// Data for -8 grade
if (cell->type == id_TRELLIS_SLICE) {
bool has_carry = cell->sliceInfo.is_carry;
if (fromPort == id_A0 || fromPort == id_B0 || fromPort == id_C0 || fromPort == id_D0 || fromPort == id_A1 ||
fromPort == id_B1 || fromPort == id_C1 || fromPort == id_D1 || fromPort == id_M0 || fromPort == id_M1 ||
fromPort == id_FXA || fromPort == id_FXB || fromPort == id_FCI) {
return getDelayFromTimingDatabase(has_carry ? id_SCCU2C : id_SLOGICB, fromPort, toPort, delay);
}
if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) ||
(fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) ||
(fromPort == id_C0 && toPort == id_WADO2) || (fromPort == id_C1 && toPort == id_WDO0) ||
(fromPort == id_D0 && toPort == id_WADO0) || (fromPort == id_D1 && toPort == id_WDO2)) {
delay.min_delay = 0;
delay.max_delay = 0;
return true;
}
return false;
} else if (cell->type == id_DCCA) {
if (fromPort == id_CLKI && toPort == id_CLKO) {
delay.min_delay = 0;
delay.max_delay = 0;
return true;
}
return false;
} else if (cell->type == id_DP16KD) {
return false;
} else if (cell->type == id_MULT18X18D) {
if (cell->multInfo.is_clocked)
return false;
std::string fn = fromPort.str(this), tn = toPort.str(this);
if (fn.size() > 1 && (fn.front() == 'A' || fn.front() == 'B') && std::isdigit(fn.at(1))) {
if (tn.size() > 1 && tn.front() == 'P' && std::isdigit(tn.at(1)))
return getDelayFromTimingDatabase(cell->multInfo.timing_id, id(std::string("") + fn.front()), id_P,
delay);
}
return false;
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
return false;
} else {
return false;
}
}
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
{
auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; };
clockInfoCount = 0;
if (cell->type == id_TRELLIS_SLICE) {
int sd0 = cell->sliceInfo.sd0, sd1 = cell->sliceInfo.sd1;
if (port == id_CLK || port == id_WCK)
return TMG_CLOCK_INPUT;
if (port == id_A0 || port == id_A1 || port == id_B0 || port == id_B1 || port == id_C0 || port == id_C1 ||
port == id_D0 || port == id_D1 || port == id_FCI || port == id_FXA || port == id_FXB)
return TMG_COMB_INPUT;
if (port == id_F0 && disconnected(id_A0) && disconnected(id_B0) && disconnected(id_C0) && disconnected(id_D0) &&
disconnected(id_FCI))
return TMG_IGNORE; // LUT with no inputs is a constant
if (port == id_F1 && disconnected(id_A1) && disconnected(id_B1) && disconnected(id_C1) && disconnected(id_D1) &&
disconnected(id_FCI))
return TMG_IGNORE; // LUT with no inputs is a constant
if (port == id_F0 || port == id_F1 || port == id_FCO || port == id_OFX0 || port == id_OFX1)
return TMG_COMB_OUTPUT;
if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 0 && port == id_M0) ||
(sd1 == 0 && port == id_M1)) {
clockInfoCount = 1;
return TMG_REGISTER_INPUT;
}
if (port == id_M0 || port == id_M1)
return TMG_COMB_INPUT;
if (port == id_Q0 || port == id_Q1) {
clockInfoCount = 1;
return TMG_REGISTER_OUTPUT;
}
if (port == id_WDO0 || port == id_WDO1 || port == id_WDO2 || port == id_WDO3 || port == id_WADO0 ||
port == id_WADO1 || port == id_WADO2 || port == id_WADO3)
return TMG_COMB_OUTPUT;
if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 ||
port == id_WAD3 || port == id_WRE) {
clockInfoCount = 1;
return TMG_REGISTER_INPUT;
}
NPNR_ASSERT_FALSE_STR("no timing type for slice port '" + port.str(this) + "'");
} else if (cell->type == id_TRELLIS_IO) {
if (port == id_T || port == id_I)
return TMG_ENDPOINT;
if (port == id_O)
return TMG_STARTPOINT;
return TMG_IGNORE;
} else if (cell->type == id_DCCA) {
if (port == id_CLKI)
return TMG_COMB_INPUT;
if (port == id_CLKO)
return TMG_COMB_OUTPUT;
return TMG_IGNORE;
} else if (cell->type == id_DP16KD) {
if (port == id_CLKA || port == id_CLKB)
return TMG_CLOCK_INPUT;
std::string port_name = port.str(this);
for (auto c : boost::adaptors::reverse(port_name)) {
if (std::isdigit(c))
continue;
if (c == 'A' || c == 'B')
clockInfoCount = 1;
else
NPNR_ASSERT_FALSE_STR("bad ram port");
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
}
NPNR_ASSERT_FALSE_STR("no timing type for RAM port '" + port.str(this) + "'");
} else if (cell->type == id_MULT18X18D) {
if (port == id_CLK0 || port == id_CLK1 || port == id_CLK2 || port == id_CLK3)
return TMG_CLOCK_INPUT;
if (port == id_CE0 || port == id_CE1 || port == id_CE2 || port == id_CE3 || port == id_RST0 ||
port == id_RST1 || port == id_RST2 || port == id_RST3 || port == id_SIGNEDA || port == id_SIGNEDB) {
if (cell->multInfo.is_clocked) {
clockInfoCount = 1;
return TMG_REGISTER_INPUT;
} else {
return TMG_COMB_INPUT;
}
}
std::string pname = port.str(this);
if (pname.size() > 1) {
if ((pname.front() == 'A' || pname.front() == 'B') && std::isdigit(pname.at(1))) {
if (cell->multInfo.is_clocked) {
clockInfoCount = 1;
return TMG_REGISTER_INPUT;
} else {
return TMG_COMB_INPUT;
}
}
if ((pname.front() == 'P') && std::isdigit(pname.at(1))) {
if (cell->multInfo.is_clocked) {
clockInfoCount = 1;
return TMG_REGISTER_OUTPUT;
} else {
return TMG_COMB_OUTPUT;
}
}
}
return TMG_IGNORE;
} else if (cell->type == id_ALU54B) {
return TMG_IGNORE; // FIXME
} else if (cell->type == id_EHXPLLL) {
return TMG_IGNORE;
} else if (cell->type == id_DCUA || cell->type == id_EXTREFB || cell->type == id_PCSCLKDIV) {
if (port == id_CH0_FF_TXI_CLK || port == id_CH0_FF_RXI_CLK || port == id_CH1_FF_TXI_CLK ||
port == id_CH1_FF_RXI_CLK)
return TMG_CLOCK_INPUT;
std::string prefix = port.str(this).substr(0, 9);
if (prefix == "CH0_FF_TX" || prefix == "CH0_FF_RX" || prefix == "CH1_FF_TX" || prefix == "CH1_FF_RX") {
clockInfoCount = 1;
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
}
return TMG_IGNORE;
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
if (port == id_CLK || port == id_ECLK) {
return TMG_CLOCK_INPUT;
} else if (port == id_IOLDO || port == id_IOLDOI || port == id_IOLDOD || port == id_IOLTO || port == id_PADDI ||
port == id_DQSR90 || port == id_DQSW || port == id_DQSW270) {
return TMG_IGNORE;
} else {
clockInfoCount = 1;
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
}
} else if (cell->type == id_DTR || cell->type == id_USRMCLK || cell->type == id_SEDGA || cell->type == id_GSR ||
cell->type == id_JTAGG) {
return (cell->ports.at(port).type == PORT_OUT) ? TMG_STARTPOINT : TMG_ENDPOINT;
} else if (cell->type == id_OSCG) {
if (port == id_OSC)
return TMG_GEN_CLOCK;
else
return TMG_IGNORE;
} else if (cell->type == id_CLKDIVF) {
if (port == id_CLKI)
return TMG_CLOCK_INPUT;
else if (port == id_RST || port == id_ALIGNWD)
return TMG_ENDPOINT;
else if (port == id_CDIVX)
return TMG_GEN_CLOCK;
else
NPNR_ASSERT_FALSE("bad clkdiv port");
} else if (cell->type == id_DQSBUFM) {
if (port == id_READ0 || port == id_READ1) {
clockInfoCount = 1;
return TMG_REGISTER_INPUT;
} else if (port == id_DATAVALID) {
clockInfoCount = 1;
return TMG_REGISTER_OUTPUT;
} else if (port == id_SCLK || port == id_ECLK || port == id_DQSI) {
return TMG_CLOCK_INPUT;
} else if (port == id_DQSR90 || port == id_DQSW || port == id_DQSW270) {
return TMG_GEN_CLOCK;
}
return (cell->ports.at(port).type == PORT_OUT) ? TMG_STARTPOINT : TMG_ENDPOINT;
} else if (cell->type == id_DDRDLL) {
if (port == id_CLK)
return TMG_CLOCK_INPUT;
return (cell->ports.at(port).type == PORT_OUT) ? TMG_STARTPOINT : TMG_ENDPOINT;
} else if (cell->type == id_TRELLIS_ECLKBUF) {
return (cell->ports.at(port).type == PORT_OUT) ? TMG_COMB_OUTPUT : TMG_COMB_INPUT;
} else if (cell->type == id_ECLKSYNCB) {
if (cell->ports.at(port).name == id_STOP)
return TMG_ENDPOINT;
return (cell->ports.at(port).type == PORT_OUT) ? TMG_COMB_OUTPUT : TMG_COMB_INPUT;
} else if (cell->type == id_ECLKBRIDGECS) {
if (cell->ports.at(port).name == id_SEL)
return TMG_ENDPOINT;
return (cell->ports.at(port).type == PORT_OUT) ? TMG_COMB_OUTPUT : TMG_COMB_INPUT;
} else {
log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this),
cell->name.c_str(this));
}
}
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
{
TimingClockingInfo info;
info.setup = getDelayFromNS(0);
info.hold = getDelayFromNS(0);
info.clockToQ = getDelayFromNS(0);
if (cell->type == id_TRELLIS_SLICE) {
int sd0 = cell->sliceInfo.sd0, sd1 = cell->sliceInfo.sd1;
if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 ||
port == id_WAD3 || port == id_WRE) {
info.edge = RISING_EDGE;
info.clock_port = id_WCK;
getSetupHoldFromTimingDatabase(id_SDPRAME, id_WCK, port, info.setup, info.hold);
} else if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 0 && port == id_M0) ||
(sd1 == 0 && port == id_M1)) {
info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE;
info.clock_port = id_CLK;
getSetupHoldFromTimingDatabase(id_SLOGICB, id_CLK, port, info.setup, info.hold);
} else {
info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE;
info.clock_port = id_CLK;
bool is_path = getDelayFromTimingDatabase(id_SLOGICB, id_CLK, port, info.clockToQ);
NPNR_ASSERT(is_path);
}
} else if (cell->type == id_DP16KD) {
std::string port_name = port.str(this);
IdString half_clock;
for (auto c : boost::adaptors::reverse(port_name)) {
if (std::isdigit(c))
continue;
if (c == 'A') {
half_clock = id_CLKA;
break;
} else if (c == 'B') {
half_clock = id_CLKB;
break;
} else
NPNR_ASSERT_FALSE_STR("bad ram port " + port.str(this));
}
if (cell->ramInfo.is_pdp) {
bool is_output = cell->ports.at(port).type == PORT_OUT;
// In PDP mode, all read signals are in CLKB domain and write signals in CLKA domain
if (is_output || port == id_OCEB || port == id_CEB || port == id_ADB5 || port == id_ADB6 ||
port == id_ADB7 || port == id_ADB8 || port == id_ADB9 || port == id_ADB10 || port == id_ADB11 ||
port == id_ADB12 || port == id_ADB13)
info.clock_port = id_CLKB;
else
info.clock_port = id_CLKA;
} else {
info.clock_port = half_clock;
}
info.edge = (str_or_default(cell->params, info.clock_port == id_CLKB ? id("CLKBMUX") : id("CLKAMUX"), "CLK") ==
"INV")
? FALLING_EDGE
: RISING_EDGE;
if (cell->ports.at(port).type == PORT_OUT) {
bool is_path = getDelayFromTimingDatabase(cell->ramInfo.regmode_timing_id, half_clock, port, info.clockToQ);
NPNR_ASSERT(is_path);
} else {
getSetupHoldFromTimingDatabase(cell->ramInfo.regmode_timing_id, half_clock, port, info.setup, info.hold);
}
} else if (cell->type == id_DCUA) {
std::string prefix = port.str(this).substr(0, 9);
info.edge = RISING_EDGE;
if (prefix == "CH0_FF_TX")
info.clock_port = id_CH0_FF_TXI_CLK;
else if (prefix == "CH0_FF_RX")
info.clock_port = id_CH0_FF_RXI_CLK;
else if (prefix == "CH1_FF_TX")
info.clock_port = id_CH1_FF_TXI_CLK;
else if (prefix == "CH1_FF_RX")
info.clock_port = id_CH1_FF_RXI_CLK;
if (cell->ports.at(port).type == PORT_OUT) {
info.clockToQ = getDelayFromNS(0.7);
} else {
info.setup = getDelayFromNS(1);
info.hold = getDelayFromNS(0);
}
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
info.clock_port = id_CLK;
if (cell->ports.at(port).type == PORT_OUT) {
info.clockToQ = getDelayFromNS(0.5);
} else {
info.setup = getDelayFromNS(0.1);
info.hold = getDelayFromNS(0);
}
} else if (cell->type == id_DQSBUFM) {
info.clock_port = id_SCLK;
if (port == id_DATAVALID) {
info.clockToQ = getDelayFromNS(0.2);
} else if (port == id_READ0 || port == id_READ1) {
info.setup = getDelayFromNS(0.5);
info.hold = getDelayFromNS(-0.4);
} else {
NPNR_ASSERT_FALSE("unknown DQSBUFM register port");
}
} else if (cell->type == id_MULT18X18D) {
std::string port_name = port.str(this);
// To keep the timing DB small, like signals (e.g. P[35:0] have been
// grouped. To look up the timing, we therefore need to map this port
// to the enclosing port group.
auto has_prefix = [](std::string base, std::string prefix) {
return base.compare(0, prefix.size(), prefix) == 0;
};
IdString port_group;
if (has_prefix(port_name, "A")) {
port_group = id_A;
} else if (has_prefix(port_name, "B")) {
port_group = id_B;
} else if (has_prefix(port_name, "P")) {
port_group = id_P;
} else if (has_prefix(port_name, "CE")) {
port_group = id_CE0;
} else if (has_prefix(port_name, "RST")) {
port_group = id_RST0;
} else if (has_prefix(port_name, "SIGNED")) {
// Both SIGNEDA and SIGNEDB exist in the DB, so can directly use these here
port_group = port;
} else {
NPNR_ASSERT_FALSE("Unknown MULT18X18D register port");
}
// If this port is clocked at all, it must be clocked from CLK0
IdString clock_id = id_CLK0;
info.clock_port = clock_id;
info.edge = RISING_EDGE;
if (cell->ports.at(port).type == PORT_OUT) {
bool is_path = getDelayFromTimingDatabase(cell->multInfo.timing_id, clock_id, port_group, info.clockToQ);
NPNR_ASSERT(is_path);
} else {
getSetupHoldFromTimingDatabase(cell->multInfo.timing_id, clock_id, port_group, info.setup, info.hold);
}
}
return info;
}
std::vector<std::pair<std::string, std::string>> Arch::getTilesAtLocation(int row, int col)
{
std::vector<std::pair<std::string, std::string>> ret;
auto &tileloc = chip_info->tile_info[row * chip_info->width + col];
for (auto &tn : tileloc.tile_names) {
ret.push_back(std::make_pair(tn.name.get(), chip_info->tiletype_names[tn.type_idx].get()));
}
return ret;
}
GlobalInfoPOD Arch::globalInfoAtLoc(Location loc)
{
int locidx = loc.y * chip_info->width + loc.x;
return chip_info->location_glbinfo[locidx];
}
bool Arch::getPIODQSGroup(BelId pio, bool &dqsright, int &dqsrow)
{
for (auto &ppio : chip_info->pio_info) {
if (Location(ppio.abs_loc) == pio.location && ppio.bel_index == pio.index) {
int dqs = ppio.dqsgroup;
if (dqs == -1)
return false;
else {
dqsright = (dqs & 2048) != 0;
dqsrow = dqs & 0x1FF;
return true;
}
}
}
NPNR_ASSERT_FALSE("failed to find PIO");
}
BelId Arch::getDQSBUF(bool dqsright, int dqsrow)
{
BelId bel;
bel.location.y = dqsrow;
bel.location.x = (dqsright ? (chip_info->width - 1) : 0);
for (int i = 0; i < int(locInfo(bel)->bel_data.size()); i++) {
auto &bd = locInfo(bel)->bel_data[i];
if (bd.type == id_DQSBUFM.index) {
bel.index = i;
return bel;
}
}
NPNR_ASSERT_FALSE("failed to find DQSBUF");
}
WireId Arch::getBankECLK(int bank, int eclk)
{
return getWireByLocAndBasename(Location(0, 0), "G_BANK" + std::to_string(bank) + "ECLK" + std::to_string(eclk));
}
#ifdef WITH_HEAP
const std::string Arch::defaultPlacer = "heap";
#else
const std::string Arch::defaultPlacer = "sa";
#endif
const std::vector<std::string> Arch::availablePlacers = {"sa",
#ifdef WITH_HEAP
"heap"
#endif
};
const std::string Arch::defaultRouter = "router1";
const std::vector<std::string> Arch::availableRouters = {"router1", "router2"};
// -----------------------------------------------------------------------
GroupId Arch::getGroupByName(IdString name) const
{
for (auto g : getGroups())
if (getGroupName(g) == name)
return g;
return GroupId();
}
IdString Arch::getGroupName(GroupId group) const
{
std::string suffix;
switch (group.type) {
case GroupId::TYPE_SWITCHBOX:
suffix = "switchbox";
break;
default:
return IdString();
}
return id("X" + std::to_string(group.location.x) + "/Y" + std::to_string(group.location.y) + "/" + suffix);
}
std::vector<GroupId> Arch::getGroups() const
{
std::vector<GroupId> ret;
for (int y = 1; y < chip_info->height - 1; y++) {
for (int x = 1; x < chip_info->width - 1; x++) {
GroupId group;
group.type = GroupId::TYPE_SWITCHBOX;
group.location.x = x;
group.location.y = y;
ret.push_back(group);
}
}
return ret;
}
std::vector<BelId> Arch::getGroupBels(GroupId group) const
{
std::vector<BelId> ret;
return ret;
}
std::vector<WireId> Arch::getGroupWires(GroupId group) const
{
std::vector<WireId> ret;
return ret;
}
std::vector<PipId> Arch::getGroupPips(GroupId group) const
{
std::vector<PipId> ret;
return ret;
}
std::vector<GroupId> Arch::getGroupGroups(GroupId group) const
{
std::vector<GroupId> ret;
return ret;
}
// -----------------------------------------------------------------------
std::vector<std::pair<IdString, std::string>> Arch::getWireAttrs(WireId wire) const
{
std::vector<std::pair<IdString, std::string>> ret;
auto &wi = locInfo(wire)->wire_data[wire.index];
ret.push_back(std::make_pair(id("TILE_WIRE_ID"), stringf("%d", wi.tile_wire)));
return ret;
}
NEXTPNR_NAMESPACE_END