2023-07-14 16:57:20 +08:00
|
|
|
#include <regex>
|
|
|
|
|
2023-06-29 04:50:16 +08:00
|
|
|
#include "himbaechel_api.h"
|
2023-07-05 10:49:25 +08:00
|
|
|
#include "himbaechel_helpers.h"
|
2023-06-29 04:50:16 +08:00
|
|
|
#include "log.h"
|
|
|
|
#include "nextpnr.h"
|
2023-07-05 10:49:25 +08:00
|
|
|
|
|
|
|
#define GEN_INIT_CONSTIDS
|
|
|
|
#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
|
|
|
|
#include "himbaechel_constids.h"
|
2023-06-29 04:50:16 +08:00
|
|
|
|
2023-07-14 16:57:20 +08:00
|
|
|
#include "cst.h"
|
2023-07-12 09:25:28 +08:00
|
|
|
#include "globals.h"
|
2023-06-30 15:18:14 +08:00
|
|
|
#include "gowin.h"
|
2023-07-20 10:09:14 +08:00
|
|
|
#include "gowin_utils.h"
|
2023-07-05 10:49:25 +08:00
|
|
|
#include "pack.h"
|
2023-06-29 04:50:16 +08:00
|
|
|
|
|
|
|
NEXTPNR_NAMESPACE_BEGIN
|
|
|
|
|
2023-07-05 10:49:25 +08:00
|
|
|
namespace {
|
|
|
|
struct GowinImpl : HimbaechelAPI
|
|
|
|
{
|
|
|
|
|
|
|
|
~GowinImpl(){};
|
|
|
|
void init_constids(Arch *arch) override { init_uarch_constids(arch); }
|
|
|
|
void init(Context *ctx) override;
|
|
|
|
|
2023-07-12 09:25:28 +08:00
|
|
|
void pack() override;
|
2023-07-05 10:49:25 +08:00
|
|
|
void prePlace() override;
|
|
|
|
void postPlace() override;
|
2023-07-12 09:25:28 +08:00
|
|
|
void preRoute() override;
|
2023-08-06 18:56:08 +08:00
|
|
|
void postRoute() override;
|
2023-07-05 10:49:25 +08:00
|
|
|
|
|
|
|
bool isBelLocationValid(BelId bel, bool explain_invalid) const override;
|
|
|
|
|
|
|
|
// Bel bucket functions
|
|
|
|
IdString getBelBucketForCellType(IdString cell_type) const override;
|
|
|
|
|
|
|
|
bool isValidBelForCellType(IdString cell_type, BelId bel) const override;
|
|
|
|
|
|
|
|
private:
|
|
|
|
HimbaechelHelpers h;
|
2023-07-20 10:09:14 +08:00
|
|
|
GowinUtils gwu;
|
2023-07-05 10:49:25 +08:00
|
|
|
|
2023-07-12 09:25:28 +08:00
|
|
|
IdString chip;
|
|
|
|
IdString partno;
|
|
|
|
|
2023-08-06 18:56:08 +08:00
|
|
|
std::set<BelId> inactive_bels;
|
|
|
|
|
2023-07-05 10:49:25 +08:00
|
|
|
// Validity checking
|
|
|
|
struct GowinCellInfo
|
|
|
|
{
|
|
|
|
const NetInfo *lut_f = nullptr;
|
|
|
|
const NetInfo *ff_d = nullptr, *ff_ce = nullptr, *ff_clk = nullptr, *ff_lsr = nullptr;
|
|
|
|
const NetInfo *alu_sum = nullptr;
|
|
|
|
};
|
|
|
|
std::vector<GowinCellInfo> fast_cell_info;
|
|
|
|
void assign_cell_info();
|
|
|
|
|
|
|
|
// bel placement validation
|
|
|
|
bool slice_valid(int x, int y, int z) const;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct GowinArch : HimbaechelArch
|
|
|
|
{
|
|
|
|
GowinArch() : HimbaechelArch("gowin"){};
|
|
|
|
std::unique_ptr<HimbaechelAPI> create(const dict<std::string, std::string> &args)
|
|
|
|
{
|
|
|
|
return std::make_unique<GowinImpl>();
|
|
|
|
}
|
|
|
|
} gowinrArch;
|
|
|
|
|
2023-07-01 17:09:27 +08:00
|
|
|
void GowinImpl::init(Context *ctx)
|
|
|
|
{
|
|
|
|
h.init(ctx);
|
|
|
|
HimbaechelAPI::init(ctx);
|
2023-07-12 09:25:28 +08:00
|
|
|
|
2023-07-20 10:09:14 +08:00
|
|
|
gwu.init(ctx);
|
|
|
|
|
2023-07-12 09:25:28 +08:00
|
|
|
const ArchArgs &args = ctx->getArchArgs();
|
2023-07-01 17:09:27 +08:00
|
|
|
// These fields go in the header of the output JSON file and can help
|
|
|
|
// gowin_pack support different architectures
|
|
|
|
ctx->settings[ctx->id("packer.arch")] = std::string("himbaechel/gowin");
|
2023-07-12 09:25:28 +08:00
|
|
|
ctx->settings[ctx->id("packer.chipdb")] = args.chipdb;
|
|
|
|
|
|
|
|
if (!args.options.count("partno")) {
|
|
|
|
log_error("Partnumber (like --vopt partno=GW1NR-LV9QN88PC6/I5) must be specified.\n");
|
|
|
|
}
|
|
|
|
// GW1N-9C.xxx -> GW1N-9C
|
|
|
|
std::string chipdb = args.chipdb;
|
|
|
|
auto dot_pos = chipdb.find(".");
|
|
|
|
if (dot_pos != std::string::npos) {
|
|
|
|
chipdb.resize(dot_pos);
|
|
|
|
}
|
|
|
|
chip = ctx->id(chipdb);
|
2023-07-14 16:57:20 +08:00
|
|
|
std::string pn = args.options.at("partno");
|
|
|
|
partno = ctx->id(pn);
|
|
|
|
ctx->settings[ctx->id("packer.partno")] = pn;
|
|
|
|
|
2023-07-19 11:29:18 +08:00
|
|
|
// package and speed class
|
2023-07-14 16:57:20 +08:00
|
|
|
std::regex speedre = std::regex("(.*)(C[0-9]/I[0-9])$");
|
|
|
|
std::smatch match;
|
|
|
|
|
|
|
|
IdString spd;
|
|
|
|
IdString package_idx;
|
|
|
|
if (std::regex_match(pn, match, speedre)) {
|
|
|
|
package_idx = ctx->id(match[1]);
|
|
|
|
spd = ctx->id(match[2]);
|
|
|
|
} else {
|
|
|
|
if (pn.length() > 2 && pn.compare(pn.length() - 2, 2, "ES")) {
|
|
|
|
package_idx = ctx->id(pn.substr(pn.length() - 2));
|
|
|
|
spd = ctx->id("ES");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx->debug) {
|
2023-07-19 11:29:18 +08:00
|
|
|
log_info("packages:%ld\n", ctx->chip_info->packages.ssize());
|
2023-07-14 16:57:20 +08:00
|
|
|
}
|
|
|
|
for (int i = 0; i < ctx->chip_info->packages.ssize(); ++i) {
|
|
|
|
if (IdString(ctx->chip_info->packages[i].name) == package_idx) {
|
|
|
|
if (ctx->debug) {
|
|
|
|
log_info("i:%d %s\n", i, package_idx.c_str(ctx));
|
|
|
|
}
|
|
|
|
ctx->package_info = &ctx->chip_info->packages[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ctx->package_info == nullptr) {
|
|
|
|
log_error("No package for partnumber %s\n", partno.c_str(ctx));
|
|
|
|
}
|
|
|
|
|
2023-07-19 11:29:18 +08:00
|
|
|
// constraints
|
2023-07-14 16:57:20 +08:00
|
|
|
if (args.options.count("cst")) {
|
|
|
|
ctx->settings[ctx->id("cst.filename")] = args.options.at("cst");
|
|
|
|
}
|
2023-06-30 15:18:14 +08:00
|
|
|
}
|
|
|
|
|
2023-07-14 16:57:20 +08:00
|
|
|
void GowinImpl::pack()
|
|
|
|
{
|
|
|
|
if (ctx->settings.count(ctx->id("cst.filename"))) {
|
|
|
|
std::string filename = ctx->settings[ctx->id("cst.filename")].as_string();
|
|
|
|
std::ifstream in(filename);
|
|
|
|
if (!in) {
|
|
|
|
log_error("failed to open CST file '%s'\n", filename.c_str());
|
|
|
|
}
|
|
|
|
if (!gowin_apply_constraints(ctx, in)) {
|
|
|
|
log_error("failed to parse CST file '%s'\n", filename.c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
gowin_pack(ctx);
|
|
|
|
}
|
2023-07-23 14:46:04 +08:00
|
|
|
|
2023-07-02 14:09:39 +08:00
|
|
|
void GowinImpl::prePlace() { assign_cell_info(); }
|
2023-07-12 09:25:28 +08:00
|
|
|
void GowinImpl::postPlace()
|
|
|
|
{
|
|
|
|
if (ctx->debug) {
|
|
|
|
log_info("================== Final Placement ===================\n");
|
|
|
|
for (auto &cell : ctx->cells) {
|
|
|
|
auto ci = cell.second.get();
|
2023-08-06 18:56:08 +08:00
|
|
|
if (ci->bel != BelId()) {
|
|
|
|
log_info("%s: %s\n", ctx->nameOfBel(ci->bel), ctx->nameOf(ci));
|
|
|
|
} else {
|
|
|
|
log_info("unknown: %s\n", ctx->nameOf(ci));
|
|
|
|
}
|
2023-07-12 09:25:28 +08:00
|
|
|
}
|
2023-07-14 16:57:20 +08:00
|
|
|
log_break();
|
2023-07-12 09:25:28 +08:00
|
|
|
}
|
|
|
|
}
|
2023-06-30 15:18:14 +08:00
|
|
|
|
2023-07-12 09:25:28 +08:00
|
|
|
void GowinImpl::preRoute() { gowin_route_globals(ctx); }
|
2023-06-30 15:18:14 +08:00
|
|
|
|
2023-08-06 18:56:08 +08:00
|
|
|
void GowinImpl::postRoute()
|
|
|
|
{
|
|
|
|
std::set<IdString> visited_hclk_users;
|
|
|
|
|
|
|
|
for (auto &cell : ctx->cells) {
|
|
|
|
auto ci = cell.second.get();
|
2023-08-13 20:05:18 +08:00
|
|
|
if (ci->type == id_IOLOGIC || (is_iologic(ci) && !ci->type.in(id_ODDR, id_ODDRC, id_IDDR, id_IDDRC))) {
|
2023-08-06 18:56:08 +08:00
|
|
|
if (visited_hclk_users.find(ci->name) == visited_hclk_users.end()) {
|
|
|
|
// mark FCLK<-HCLK connections
|
|
|
|
const NetInfo *h_net = ci->getPort(id_FCLK);
|
|
|
|
if (h_net) {
|
2023-08-07 16:20:08 +08:00
|
|
|
for (auto &user : h_net->users) {
|
2023-08-06 18:56:08 +08:00
|
|
|
if (user.port != id_FCLK) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-08-08 08:57:45 +08:00
|
|
|
user.cell->setAttr(id_IOLOGIC_FCLK, Property("UNKNOWN"));
|
2023-08-06 18:56:08 +08:00
|
|
|
visited_hclk_users.insert(user.cell->name);
|
|
|
|
// XXX Based on the implementation, perhaps a function
|
|
|
|
// is needed to get Pip from a Wire
|
|
|
|
PipId up_pip = h_net->wires.at(ctx->getNetinfoSinkWire(h_net, user, 0)).pip;
|
|
|
|
IdString up_wire_name = ctx->getWireName(ctx->getPipSrcWire(up_pip))[1];
|
|
|
|
if (up_wire_name.in(id_HCLK_OUT0, id_HCLK_OUT1, id_HCLK_OUT2, id_HCLK_OUT3)) {
|
2023-08-07 16:20:08 +08:00
|
|
|
user.cell->setAttr(id_IOLOGIC_FCLK, Property(up_wire_name.str(ctx)));
|
2023-08-13 20:05:18 +08:00
|
|
|
if (ctx->debug) {
|
|
|
|
log_info("set IOLOGIC_FCLK to %s\n", up_wire_name.c_str(ctx));
|
|
|
|
}
|
2023-08-06 18:56:08 +08:00
|
|
|
}
|
|
|
|
if (ctx->debug) {
|
|
|
|
log_info("HCLK user cell:%s, port:%s, wire:%s, pip:%s, up wire:%s\n",
|
|
|
|
ctx->nameOf(user.cell), user.port.c_str(ctx),
|
|
|
|
ctx->nameOfWire(ctx->getNetinfoSinkWire(h_net, user, 0)), ctx->nameOfPip(up_pip),
|
|
|
|
ctx->nameOfWire(ctx->getPipSrcWire(up_pip)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-01 17:09:27 +08:00
|
|
|
bool GowinImpl::isBelLocationValid(BelId bel, bool explain_invalid) const
|
|
|
|
{
|
|
|
|
Loc l = ctx->getBelLocation(bel);
|
2023-07-05 10:49:25 +08:00
|
|
|
if (!ctx->getBoundBelCell(bel)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
IdString bel_type = ctx->getBelType(bel);
|
2023-07-06 12:48:44 +08:00
|
|
|
switch (bel_type.hash()) {
|
|
|
|
case ID_LUT4: /* fall-through */
|
|
|
|
case ID_DFF:
|
2023-07-01 17:09:27 +08:00
|
|
|
return slice_valid(l.x, l.y, l.z / 2);
|
2023-07-06 12:48:44 +08:00
|
|
|
case ID_ALU:
|
|
|
|
return slice_valid(l.x, l.y, l.z - BelZ::ALU0_Z);
|
|
|
|
case ID_RAM16SDP4:
|
|
|
|
// only slices 4 and 5 are critical for RAM
|
|
|
|
return slice_valid(l.x, l.y, l.z - BelZ::RAMW_Z + 5) && slice_valid(l.x, l.y, l.z - BelZ::RAMW_Z + 4);
|
2023-07-01 17:09:27 +08:00
|
|
|
}
|
2023-07-05 10:49:25 +08:00
|
|
|
return true;
|
2023-06-30 15:18:14 +08:00
|
|
|
}
|
2023-06-29 04:50:16 +08:00
|
|
|
|
2023-06-30 15:18:14 +08:00
|
|
|
// Bel bucket functions
|
2023-07-01 17:09:27 +08:00
|
|
|
IdString GowinImpl::getBelBucketForCellType(IdString cell_type) const
|
|
|
|
{
|
|
|
|
if (cell_type.in(id_IBUF, id_OBUF)) {
|
|
|
|
return id_IOB;
|
|
|
|
}
|
|
|
|
if (type_is_lut(cell_type)) {
|
|
|
|
return id_LUT4;
|
|
|
|
}
|
|
|
|
if (type_is_dff(cell_type)) {
|
|
|
|
return id_DFF;
|
|
|
|
}
|
2023-07-06 12:48:44 +08:00
|
|
|
if (type_is_ssram(cell_type)) {
|
|
|
|
return id_RAM16SDP4;
|
|
|
|
}
|
2023-08-06 18:56:08 +08:00
|
|
|
if (type_is_iologic(cell_type)) {
|
|
|
|
return id_IOLOGIC;
|
|
|
|
}
|
2023-07-01 17:09:27 +08:00
|
|
|
if (cell_type == id_GOWIN_GND) {
|
|
|
|
return id_GND;
|
|
|
|
}
|
|
|
|
if (cell_type == id_GOWIN_VCC) {
|
|
|
|
return id_VCC;
|
|
|
|
}
|
|
|
|
return cell_type;
|
2023-06-30 15:18:14 +08:00
|
|
|
}
|
|
|
|
|
2023-07-01 17:09:27 +08:00
|
|
|
bool GowinImpl::isValidBelForCellType(IdString cell_type, BelId bel) const
|
|
|
|
{
|
|
|
|
IdString bel_type = ctx->getBelType(bel);
|
|
|
|
if (bel_type == id_IOB) {
|
|
|
|
return cell_type.in(id_IBUF, id_OBUF);
|
|
|
|
}
|
|
|
|
if (bel_type == id_LUT4) {
|
|
|
|
return type_is_lut(cell_type);
|
|
|
|
}
|
|
|
|
if (bel_type == id_DFF) {
|
|
|
|
return type_is_dff(cell_type);
|
|
|
|
}
|
2023-07-06 12:48:44 +08:00
|
|
|
if (bel_type == id_RAM16SDP4) {
|
|
|
|
return type_is_ssram(cell_type);
|
|
|
|
}
|
2023-08-06 18:56:08 +08:00
|
|
|
if (bel_type == id_IOLOGIC) {
|
|
|
|
return type_is_iologic(cell_type);
|
|
|
|
}
|
2023-07-01 17:09:27 +08:00
|
|
|
if (bel_type == id_GND) {
|
|
|
|
return cell_type == id_GOWIN_GND;
|
|
|
|
}
|
|
|
|
if (bel_type == id_VCC) {
|
|
|
|
return cell_type == id_GOWIN_VCC;
|
|
|
|
}
|
|
|
|
return (bel_type == cell_type);
|
2023-06-30 15:18:14 +08:00
|
|
|
}
|
|
|
|
|
2023-07-01 17:09:27 +08:00
|
|
|
void GowinImpl::assign_cell_info()
|
|
|
|
{
|
|
|
|
fast_cell_info.resize(ctx->cells.size());
|
|
|
|
for (auto &cell : ctx->cells) {
|
|
|
|
CellInfo *ci = cell.second.get();
|
|
|
|
auto &fc = fast_cell_info.at(ci->flat_index);
|
|
|
|
if (is_lut(ci)) {
|
|
|
|
fc.lut_f = ci->getPort(id_F);
|
2023-07-05 10:49:25 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (is_dff(ci)) {
|
2023-07-01 17:09:27 +08:00
|
|
|
fc.ff_d = ci->getPort(id_D);
|
|
|
|
fc.ff_clk = ci->getPort(id_CLK);
|
|
|
|
fc.ff_ce = ci->getPort(id_CE);
|
|
|
|
for (IdString port : {id_SET, id_RESET, id_PRESET, id_CLEAR}) {
|
|
|
|
fc.ff_lsr = ci->getPort(port);
|
|
|
|
if (fc.ff_lsr != nullptr) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-07-05 10:49:25 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (is_alu(ci)) {
|
|
|
|
fc.alu_sum = ci->getPort(id_SUM);
|
|
|
|
continue;
|
2023-07-01 17:09:27 +08:00
|
|
|
}
|
|
|
|
}
|
2023-06-30 15:18:14 +08:00
|
|
|
}
|
|
|
|
|
2023-08-18 04:40:31 +08:00
|
|
|
// DFFs must be same type or compatible
|
|
|
|
inline bool incompatible_ffs(const CellInfo *ff, const CellInfo *adj_ff)
|
|
|
|
{
|
|
|
|
return ff->type != adj_ff->type &&
|
|
|
|
((ff->type == id_DFFS && adj_ff->type != id_DFFR) || (ff->type == id_DFFR && adj_ff->type != id_DFFS) ||
|
|
|
|
(ff->type == id_DFFSE && adj_ff->type != id_DFFRE) || (ff->type == id_DFFRE && adj_ff->type != id_DFFSE) ||
|
|
|
|
(ff->type == id_DFFP && adj_ff->type != id_DFFC) || (ff->type == id_DFFC && adj_ff->type != id_DFFP) ||
|
|
|
|
(ff->type == id_DFFPE && adj_ff->type != id_DFFCE) || (ff->type == id_DFFCE && adj_ff->type != id_DFFPE) ||
|
|
|
|
(ff->type == id_DFFNS && adj_ff->type != id_DFFNR) || (ff->type == id_DFFNR && adj_ff->type != id_DFFNS) ||
|
|
|
|
(ff->type == id_DFFNSE && adj_ff->type != id_DFFNRE) ||
|
|
|
|
(ff->type == id_DFFNRE && adj_ff->type != id_DFFNSE) ||
|
|
|
|
(ff->type == id_DFFNP && adj_ff->type != id_DFFNC) || (ff->type == id_DFFNC && adj_ff->type != id_DFFNP) ||
|
|
|
|
(ff->type == id_DFFNPE && adj_ff->type != id_DFFNCE) ||
|
|
|
|
(ff->type == id_DFFNCE && adj_ff->type != id_DFFNPE));
|
|
|
|
}
|
|
|
|
|
2023-07-05 10:49:25 +08:00
|
|
|
// placement validation
|
2023-07-01 17:09:27 +08:00
|
|
|
bool GowinImpl::slice_valid(int x, int y, int z) const
|
|
|
|
{
|
|
|
|
const CellInfo *lut = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2)));
|
|
|
|
const CellInfo *ff = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2 + 1)));
|
2023-07-05 10:49:25 +08:00
|
|
|
const CellInfo *alu = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z + BelZ::ALU0_Z)));
|
2023-07-06 12:48:44 +08:00
|
|
|
const CellInfo *ramw =
|
|
|
|
(z == 4 || z == 5) ? ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, BelZ::RAMW_Z))) : nullptr;
|
2023-07-05 10:49:25 +08:00
|
|
|
|
|
|
|
if (alu && lut) {
|
2023-07-01 17:09:27 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-07-06 12:48:44 +08:00
|
|
|
if (ramw) {
|
|
|
|
if (alu || ff || lut) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-07-05 10:49:25 +08:00
|
|
|
// check for ALU/LUT in the adjacent cell
|
|
|
|
int adj_lut_z = (1 - (z & 1) * 2 + z) * 2;
|
|
|
|
int adj_alu_z = adj_lut_z / 2 + BelZ::ALU0_Z;
|
|
|
|
const CellInfo *adj_lut = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, adj_lut_z)));
|
|
|
|
const CellInfo *adj_ff = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, adj_lut_z + 1)));
|
|
|
|
const CellInfo *adj_alu = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, adj_alu_z)));
|
|
|
|
|
|
|
|
if ((alu && (adj_lut || (adj_ff && !adj_alu))) || ((lut || (ff && !alu)) && adj_alu)) {
|
|
|
|
return false;
|
2023-07-01 17:09:27 +08:00
|
|
|
}
|
2023-06-30 15:18:14 +08:00
|
|
|
|
2023-07-05 10:49:25 +08:00
|
|
|
// if there is DFF it must be connected to this LUT or ALU
|
|
|
|
if (ff) {
|
|
|
|
const auto &ff_data = fast_cell_info.at(ff->flat_index);
|
|
|
|
if (lut) {
|
|
|
|
const auto &lut_data = fast_cell_info.at(lut->flat_index);
|
|
|
|
if (ff_data.ff_d != lut_data.lut_f) {
|
|
|
|
return false;
|
|
|
|
}
|
2023-07-01 17:09:27 +08:00
|
|
|
}
|
2023-07-05 10:49:25 +08:00
|
|
|
if (alu) {
|
|
|
|
const auto &alu_data = fast_cell_info.at(alu->flat_index);
|
|
|
|
if (ff_data.ff_d != alu_data.alu_sum) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (adj_ff) {
|
2023-08-18 04:40:31 +08:00
|
|
|
if (incompatible_ffs(ff, adj_ff)) {
|
2023-07-05 10:49:25 +08:00
|
|
|
return false;
|
|
|
|
}
|
2023-07-01 17:09:27 +08:00
|
|
|
|
2023-07-05 10:49:25 +08:00
|
|
|
// CE, LSR and CLK must match
|
|
|
|
const auto &adj_ff_data = fast_cell_info.at(adj_ff->flat_index);
|
|
|
|
if (adj_ff_data.ff_lsr != ff_data.ff_lsr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (adj_ff_data.ff_clk != ff_data.ff_clk) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (adj_ff_data.ff_ce != ff_data.ff_ce) {
|
|
|
|
return false;
|
2023-07-01 17:09:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-07-05 10:49:25 +08:00
|
|
|
return true;
|
2023-06-30 15:18:14 +08:00
|
|
|
}
|
|
|
|
|
2023-07-05 10:49:25 +08:00
|
|
|
} // namespace
|
2023-07-02 14:09:39 +08:00
|
|
|
|
2023-06-29 04:50:16 +08:00
|
|
|
NEXTPNR_NAMESPACE_END
|