nextpnr/himbaechel/uarch/xilinx/xilinx_place.cc
gatecat e4dfd4e622 xilinx: Support single-port LUTRAM variants
Signed-off-by: gatecat <gatecat@ds0.me>
2024-09-26 18:11:01 +02:00

637 lines
26 KiB
C++

/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2019-2023 gatecat <gatecat@ds0.me>
*
* 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 <boost/algorithm/string.hpp>
#include <regex>
#include "extra_data.h"
#include "himbaechel_api.h"
#include "log.h"
#include "nextpnr.h"
#include "util.h"
#include "xilinx.h"
#define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc"
#include "himbaechel_constids.h"
NEXTPNR_NAMESPACE_BEGIN
// #define DEBUG_VALIDITY
#ifdef DEBUG_VALIDITY
#define DBG() log_info("invalid: %s %d\n", __FILE__, __LINE__)
#else
#define DBG()
#endif
bool XilinxImpl::xc7_logic_tile_valid(IdString tile_type, const LogicTileStatus &lts) const
{
bool is_slicem = (tile_type == id_CLBLM_L) || (tile_type == id_CLBLM_R);
bool tile_is_memory = false;
if (lts.cells[(3 << 4) | BEL_6LUT] != nullptr && get_tags(lts.cells[(3 << 4) | BEL_6LUT])->lut.is_memory)
tile_is_memory = true;
bool small_memory = false;
if (lts.cells[(3 << 4) | BEL_5LUT] != nullptr && get_tags(lts.cells[(3 << 4) | BEL_5LUT])->lut.is_memory)
small_memory = true;
NetInfo *wclk = nullptr;
// Check eight-tiles (mostly LUT-related validity)
for (int i = 0; i < 8; i++) {
if (lts.eights[i].dirty) {
lts.eights[i].dirty = false;
lts.eights[i].valid = false;
auto lut6 = get_tags(lts.cells[(i << 4) | BEL_6LUT]);
auto lut5 = get_tags(lts.cells[(i << 4) | BEL_5LUT]);
// Check 6LUT
if (lut6) {
if (!is_slicem && (lut6->lut.is_memory || lut6->lut.is_srl)) {
DBG();
return false;
} // Memory and SRLs only valid in SLICEMs
if (lut6->lut.is_srl && (i >= 4)) {
DBG();
return false;
}
if (lut6->lut.is_memory || lut6->lut.is_srl) {
if (wclk == nullptr)
wclk = lut6->lut.wclk;
else if (lut6->lut.wclk != wclk) {
DBG();
return false;
}
}
if (lut5) {
// Can't mix memory and non-memory
if (lut6->lut.is_memory != lut5->lut.is_memory || lut6->lut.is_srl != lut5->lut.is_srl) {
DBG();
return false;
}
// If all 6 inputs or 2 outputs are used, 5LUT can't also be present
if (lut6->lut.input_count == 6 || lut6->lut.output_count == 2) {
DBG();
return false;
}
// If more than 5 total inputs are used, need to check number of shared input
if ((lut6->lut.input_count + lut5->lut.input_count) > 5) {
int shared = 0, need_shared = (lut6->lut.input_count + lut5->lut.input_count - 5);
for (int j = 0; j < lut6->lut.input_count; j++) {
for (int k = 0; k < lut5->lut.input_count; k++) {
if (lut6->lut.input_sigs[j] == lut5->lut.input_sigs[k])
shared++;
if (shared >= need_shared)
break;
}
}
if (shared < need_shared) {
DBG();
return false;
}
}
}
}
if (lut5 != nullptr) {
if (!is_slicem && (lut5->lut.is_memory || lut5->lut.is_srl)) {
DBG();
return false; // Memory and SRLs only valid in SLICEMs
}
if (lut5->lut.is_srl) {
if (wclk == nullptr)
wclk = lut5->lut.wclk;
else if (lut5->lut.wclk != wclk) {
DBG();
return false;
}
}
// 5LUT can use at most 5 inputs and 1 output
if (lut5->lut.input_count > 5 || lut5->lut.output_count == 2) {
DBG();
return false; // Memory and SRLs only valid in SLICEMs
}
}
// Check (over)usage ofX inputs
NetInfo *x_net = nullptr;
if (lut6) {
x_net = lut6->lut.di2_net;
}
CellInfo *mux_cell = nullptr;
// Eights A, C, E, G: F7MUX uses X input
if (i == 0 || i == 2 || i == 4 || i == 6)
mux_cell = lts.cells[i << 4 | BEL_F7MUX];
// Eights B, F: F8MUX uses X input
if (i == 1 || i == 5)
mux_cell = lts.cells[(i - 1) << 4 | BEL_F8MUX];
auto mux = get_tags(mux_cell);
if (mux) {
if (!x_net)
x_net = mux->mux.sel;
else if (x_net != mux->mux.sel) {
DBG();
return false; // conflict between existing X use and mux select
}
}
CellInfo *out_fmux_cell = nullptr;
// Subslices A, C: F7MUX connects to F7F8 out
if (i == 0 || i == 2 || i == 4 || i == 6)
out_fmux_cell = lts.cells[(i << 4) | BEL_F7MUX];
// Subslices B: F8MUX connects to F7F8 out
if (i == 1 || i == 5)
out_fmux_cell = lts.cells[(i - 1) << 4 | BEL_F8MUX];
auto carry4 = get_tags(lts.cells[((i / 4) << 6) | BEL_CARRY4]);
if (carry4 != nullptr && carry4->carry.x_sigs[i % 4] != nullptr) {
if (x_net == nullptr)
x_net = carry4->carry.x_sigs[i % 4];
else if (x_net != carry4->carry.x_sigs[i % 4]) {
DBG();
return false;
}
}
// FF1 might use X, if it isn't driven directly
auto ff1 = get_tags(lts.cells[i << 4 | BEL_FF]);
if (ff1 != nullptr && ff1->ff.d != nullptr && ff1->ff.d->driver.cell != nullptr) {
auto &drv = ff1->ff.d->driver;
if ((drv.cell == lts.cells[(i << 4) | BEL_6LUT] && drv.port != id_MC31) ||
drv.cell == lts.cells[(i << 4) | BEL_5LUT] || drv.cell == out_fmux_cell) {
// Direct, OK
} else {
// Indirect, must use X input
if (x_net == nullptr)
x_net = ff1->ff.d;
else if (x_net != ff1->ff.d) {
DBG();
return false;
}
}
}
// FF2 might use X, if it isn't driven directly
auto ff2 = get_tags(lts.cells[i << 4 | BEL_FF2]);
if (ff2 != nullptr && ff2->ff.d != nullptr && ff2->ff.d->driver.cell != nullptr) {
auto &drv = ff2->ff.d->driver;
if (drv.cell == lts.cells[(i << 4) | BEL_5LUT]) {
// Direct, OK
} else {
// Indirect, must use X input
if (x_net == nullptr)
x_net = ff2->ff.d;
else if (x_net != ff2->ff.d) {
DBG();
return false;
}
}
}
// collision with top address bits
if (tile_is_memory && !small_memory) {
auto top_lut = get_tags(lts.cells[(3 << 4) | BEL_6LUT]);
if (top_lut) {
if ((i == 2) && x_net != top_lut->lut.address_msb[0]) {
DBG();
return false;
}
if ((i == 1) && x_net != top_lut->lut.address_msb[1]) {
DBG();
return false;
}
}
}
bool mux_output_used = false;
NetInfo *out5 = nullptr;
if (lut6 != nullptr && lut6->lut.output_count == 2)
out5 = lut6->lut.output_sigs[1];
else if (lut5 != nullptr && !lut5->lut.only_drives_carry)
out5 = lut5->lut.output_sigs[0];
if (out5 != nullptr && (out5->users.entries() > 1 ||
((ff1 == nullptr || out5 != ff1->ff.d) && (ff2 == nullptr || out5 != ff2->ff.d)))) {
mux_output_used = true;
}
if (carry4 != nullptr && carry4->carry.out_sigs[i % 4] != nullptr) {
// FIXME: direct connections to FF
if (mux_output_used) {
DBG();
return false; // Memory and SRLs only valid in SLICEMs
}
mux_output_used = true;
}
if (out_fmux_cell != nullptr) {
auto out_fmux = get_tags(out_fmux_cell);
NetInfo *f7f8 = out_fmux->mux.out;
if (f7f8 != nullptr && (f7f8->users.entries() > 1 || ((ff1 == nullptr || f7f8 != ff1->ff.d)))) {
if (mux_output_used) {
DBG();
return false; // Memory and SRLs only valid in SLICEMs
}
mux_output_used = true;
}
}
if (ff2 != nullptr) {
if (mux_output_used) {
DBG();
return false; // Memory and SRLs only valid in SLICEMs
}
mux_output_used = true;
}
lts.eights[i].valid = true;
} else if (!lts.eights[i].valid) {
DBG();
return false;
}
}
// Check half-tiles
for (int i = 0; i < 2; i++) {
if (lts.halfs[i].dirty) {
lts.halfs[i].valid = false;
bool found_ff[2] = {false, false};
if (i == 0 && wclk == nullptr) {
// Need to check wclk too
for (int z = 4 * i; z < 4 * (i + 1); z++) {
for (int k = 0; k < 2; k++) {
auto lut = get_tags(lts.cells[z << 4 | (BEL_6LUT + k)]);
if (!lut)
continue;
if (!lut->lut.is_memory && !lut->lut.is_srl)
continue;
if (lut->lut.wclk != nullptr) {
wclk = lut->lut.wclk;
break;
}
}
}
}
NetInfo *clk = nullptr, *sr = nullptr, *ce = nullptr;
bool clkinv = false, srinv = false, islatch = false, ffsync = false;
for (int z = 4 * i; z < 4 * (i + 1); z++) {
for (int k = 0; k < 2; k++) {
auto ff = get_tags(lts.cells[z << 4 | (BEL_FF + k)]);
if (ff == nullptr)
continue;
if (ff->ff.is_latch && k == 1) {
DBG();
return false;
}
if (found_ff[0] || found_ff[1]) {
if (ff->ff.clk != clk) {
DBG();
return false;
}
if (ff->ff.sr != sr) {
DBG();
return false;
}
if (ff->ff.ce != ce) {
DBG();
return false;
}
if (ff->ff.is_clkinv != clkinv) {
DBG();
return false;
}
if (ff->ff.is_srinv != srinv) {
DBG();
return false;
}
if (ff->ff.is_latch != islatch) {
DBG();
return false;
}
if (ff->ff.ffsync != ffsync) {
DBG();
return false;
}
} else {
clk = ff->ff.clk;
if (i == 0 && wclk != nullptr && clk != wclk) {
DBG();
return false;
}
sr = ff->ff.sr;
ce = ff->ff.ce;
clkinv = ff->ff.is_clkinv;
srinv = ff->ff.is_srinv;
islatch = ff->ff.is_latch;
ffsync = ff->ff.ffsync;
}
found_ff[k] = true;
}
}
lts.halfs[i].valid = true;
} else if (!lts.halfs[i].valid) {
DBG();
return false;
}
}
return true;
}
bool XilinxImpl::isBelLocationValid(BelId bel, bool explain_invalid) const
{
if (is_logic_tile(bel)) {
if (!tile_status.at(bel.tile).lts)
return true;
return xc7_logic_tile_valid(bel_tile_type(bel), *tile_status.at(bel.tile).lts);
} else if (is_bram_tile(bel)) {
const auto &bts = tile_status.at(bel.tile).bts;
if (!bts)
return true;
auto onehot = [&](CellInfo *a, CellInfo *b, CellInfo *c) {
return (((a != nullptr) ? 1 : 0) + ((b != nullptr) ? 1 : 0) + ((c != nullptr) ? 1 : 0)) <= 1;
};
// Only one type of BRAM cell at any given location
if (!onehot(bts->cells[BEL_RAMFIFO36], bts->cells[BEL_RAM36], bts->cells[BEL_FIFO36])) {
DBG();
return false;
}
if (!onehot(bts->cells[BEL_RAMFIFO18_L], bts->cells[BEL_RAM18_L], bts->cells[BEL_FIFO18_L])) {
DBG();
return false;
}
// 18-bit BRAMs cannot be used whilst 36-bit is used
if (bts->cells[BEL_RAMFIFO36] || bts->cells[BEL_RAM36] || bts->cells[BEL_FIFO36]) {
for (int i = 4; i < 12; i++)
if (bts->cells[i]) {
DBG();
return false;
}
}
}
return true;
}
void XilinxImpl::fixup_placement()
{
log_info("Running post-placement legalisation...\n");
for (auto &ts : tile_status) {
if (!ts.lts)
continue;
auto &lt = *(ts.lts);
for (int z = 0; z < 8; z++) {
// Fixup LUT connectivity - applies whenever a LUT5 is used
CellInfo *lut5 = lt.cells[z << 4 | BEL_5LUT];
if (!lut5)
continue;
auto l5_tags = get_tags(lut5);
dict<IdString, std::vector<int>> lut5Inputs, lut6Inputs;
for (int i = 0; i < l5_tags->lut.input_count; i++)
if (l5_tags->lut.input_sigs[i])
lut5Inputs[l5_tags->lut.input_sigs[i]->name].push_back(i);
CellInfo *lut6 = lt.cells[z << 4 | BEL_6LUT];
if (lut6) {
auto l6_tags = get_tags(lut6);
for (int i = 0; i < l6_tags->lut.input_count; i++)
if (l6_tags->lut.input_sigs[i])
lut6Inputs[l6_tags->lut.input_sigs[i]->name].push_back(i);
}
if (l5_tags->lut.is_memory || l5_tags->lut.is_srl) {
if (lut6) {
if (!lut6->ports.count(id_A6)) {
lut6->addInput(id_A6);
}
lut6->connectPort(id_A6, ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get());
}
continue;
}
std::set<IdString> uniqueInputs;
for (auto i5 : lut5Inputs)
uniqueInputs.insert(i5.first);
for (auto i6 : lut6Inputs)
uniqueInputs.insert(i6.first);
// Disconnect LUT inputs, and re-connect them to not overlap
IdString ports[6] = {id_A1, id_A2, id_A3, id_A4, id_A5, id_A6};
for (auto p : ports) {
lut5->disconnectPort(p);
lut5->attrs.erase(ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx)));
if (lut6) {
lut6->attrs.erase(ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx)));
lut6->disconnectPort(p);
}
}
int index = 0;
for (auto i : uniqueInputs) {
if (lut5Inputs.count(i)) {
if (!lut5->ports.count(ports[index])) {
lut5->ports[ports[index]].name = ports[index];
lut5->ports[ports[index]].type = PORT_IN;
}
lut5->connectPort(ports[index], ctx->nets.at(i).get());
lut5->attrs[ctx->idf("X_ORIG_PORT_%s", ports[index].c_str(ctx))] = std::string("");
bool first = true;
for (auto inp : lut5Inputs[i]) {
lut5->attrs[ctx->idf("X_ORIG_PORT_%s", ports[index].c_str(ctx))].str +=
(first ? "I" : " I") + std::to_string(inp);
first = false;
}
}
if (lut6 && lut6Inputs.count(i)) {
if (!lut6->ports.count(ports[index])) {
lut6->ports[ports[index]].name = ports[index];
lut6->ports[ports[index]].type = PORT_IN;
}
lut6->connectPort(ports[index], ctx->nets.at(i).get());
lut6->attrs[ctx->idf("X_ORIG_PORT_%s", ports[index].c_str(ctx))] = std::string("");
bool first = true;
for (auto inp : lut6Inputs[i]) {
lut6->attrs[ctx->idf("X_ORIG_PORT_%s", ports[index].c_str(ctx))].str +=
(first ? "I" : " I") + std::to_string(inp);
first = false;
}
}
++index;
}
lut5->renamePort(id_O6, id_O5);
lut5->attrs.erase(id_X_ORIG_PORT_O6);
lut5->attrs[id_X_ORIG_PORT_O5] = std::string("O");
if (lut6) {
if (!lut6->ports.count(id_A6)) {
lut6->addInput(id_A6);
}
lut6->connectPort(id_A6, ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get());
}
}
}
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ci->type == id_PS7_PS7) {
log_info("Tieing unused PS7 inputs to constants...\n");
for (IdString pname : ctx->getBelPins(ci->bel)) {
if (ci->ports.count(pname) && ci->ports.at(pname).net != nullptr &&
ci->ports.at(pname).net->driver.cell != nullptr)
continue;
if (ctx->getBelPinType(ci->bel, pname) != PORT_IN)
continue;
std::string name = pname.str(ctx);
if (name.find("_PAD_") != std::string::npos)
continue;
if (boost::starts_with(name, "TEST") || boost::starts_with(name, "DEBUGSELECT") ||
boost::starts_with(name, "MIO") || boost::starts_with(name, "DDR"))
continue;
bool constval = false;
ci->ports[pname].name = pname;
ci->ports[pname].type = PORT_IN;
if (ci->ports[pname].net != nullptr) {
ci->disconnectPort(pname);
ci->attrs.erase(ctx->idf("X_ORIG_PORT_", name.c_str()));
}
ci->connectPort(pname,
ctx->nets.at(constval ? ctx->id("$PACKER_VCC_NET") : ctx->id("$PACKER_GND_NET")).get());
}
}
}
}
void XilinxImpl::fixup_routing()
{
log_info("Running post-routing legalisation...\n");
/*
* Convert LUT permutation into correct physical connections (i.e. effectively eliminating the permutation pips),
* then specifying the permutation as a new physical-to-logical mapping using X_ORIG_PORT. This keeps RapidWright
* and Vivado happy, preserving the original logical netlist
*/
dict<int, std::vector<int>> used_perm_pips; // tile -> [extra_data] for LUT perm pips
for (auto &net : ctx->nets) {
NetInfo *ni = net.second.get();
for (auto &wire : ni->wires) {
PipId pip = wire.second.pip;
if (pip == PipId())
continue;
auto &pd = chip_pip_info(ctx->chip_info, pip);
if (pd.flags != PIP_LUT_PERMUTATION)
continue;
const auto &extra_data = *reinterpret_cast<const XlnxPipExtraDataPOD *>(pd.extra_data.get());
used_perm_pips[pip.tile].push_back(extra_data.pip_config);
}
}
for (size_t ti = 0; ti < tile_status.size(); ti++) {
if (!used_perm_pips.count(int(ti)))
continue;
auto &ts = tile_status.at(ti);
if (!ts.lts)
continue;
auto &lt = *ts.lts;
for (int z = 0; z < 8; z++) {
CellInfo *lut5 = lt.cells[z << 4 | BEL_5LUT];
CellInfo *lut6 = lt.cells[z << 4 | BEL_6LUT];
if (lut5 == nullptr && lut6 == nullptr)
continue;
auto &pp = used_perm_pips.at(ti);
// from -> to
dict<IdString, std::vector<IdString>> new_connections;
IdString ports[6] = {id_A1, id_A2, id_A3, id_A4, id_A5, id_A6};
for (auto pip : pp) {
if (((pip >> 8) & 0xF) != z)
continue;
new_connections[ports[(pip >> 4) & 0xF]].push_back(ports[pip & 0xF]);
}
dict<IdString, NetInfo *> orig_nets;
dict<IdString, std::string> orig_ports_l6, orig_ports_l5;
for (int i = 0; i < 6; i++) {
NetInfo *l6net = lut6 ? lut6->getPort(ports[i]) : nullptr;
NetInfo *l5net = lut5 ? lut5->getPort(ports[i]) : nullptr;
orig_nets[ports[i]] = (l6net ? l6net : l5net);
if (lut6)
orig_ports_l6[ports[i]] =
str_or_default(lut6->attrs, ctx->idf("X_ORIG_PORT_%s", ports[i].c_str(ctx)));
if (lut5)
orig_ports_l5[ports[i]] =
str_or_default(lut5->attrs, ctx->idf("X_ORIG_PORT_%s", ports[i].c_str(ctx)));
}
for (auto &nc : new_connections) {
if (lut6)
lut6->disconnectPort(nc.first);
if (lut5)
lut5->disconnectPort(nc.first);
for (auto &dst : nc.second) {
if (lut6)
lut6->disconnectPort(dst);
if (lut5)
lut5->disconnectPort(dst);
}
}
for (int i = 0; i < 6; i++) {
if (lut6)
lut6->attrs.erase(ctx->idf("X_ORIG_PORT_%s", ports[i].c_str(ctx)));
if (lut5)
lut5->attrs.erase(ctx->idf("X_ORIG_PORT_%s", ports[i].c_str(ctx)));
}
for (int i = 0; i < 6; i++) {
auto p = ports[i];
if (!new_connections.count(p) || new_connections.at(p).empty())
continue;
if (lut6) {
if (!lut6->ports.count(p)) {
lut6->addInput(p);
}
lut6->connectPort(p, orig_nets[new_connections.at(p).front()]);
lut6->attrs[ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx))] = std::string("");
auto &orig_attr = lut6->attrs[ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx))].str;
bool first = true;
for (auto &nc : new_connections.at(p)) {
orig_attr += orig_ports_l6[nc] + (first ? "" : " ");
first = false;
}
if (orig_attr.empty())
lut6->attrs.erase(ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx)));
}
if (lut5) {
if (!lut5->ports.count(p)) {
lut5->addInput(p);
}
lut5->connectPort(p, orig_nets[new_connections.at(p).front()]);
lut5->attrs[ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx))] = std::string("");
auto &orig_attr = lut5->attrs[ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx))].str;
bool first = true;
for (auto &nc : new_connections.at(p)) {
orig_attr += orig_ports_l5[nc] + (first ? "" : " ");
first = false;
}
if (orig_attr.empty())
lut5->attrs.erase(ctx->idf("X_ORIG_PORT_%s", p.c_str(ctx)));
}
}
}
}
/*
* Legalise route through OSERDESE3s
*/
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ci->type == id_OSERDESE3) {
if (ci->getPort(id_T_OUT) != nullptr)
continue;
ci->params[id_OSERDES_T_BYPASS] = std::string("TRUE");
}
}
}
NEXTPNR_NAMESPACE_END