More of making it inline

This commit is contained in:
Miodrag Milanovic 2023-03-30 19:04:11 +02:00 committed by myrtle
parent ca3d32e5ac
commit 153144022f
5 changed files with 413 additions and 238 deletions

View File

@ -395,21 +395,33 @@ delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdStr
bool Arch::place()
{
std::string placer = str_or_default(settings, id_placer, defaultPlacer);
if (placer == "sa") {
bool retVal = placer1(getCtx(), Placer1Cfg(getCtx()));
getCtx()->settings[id_place] = 1;
archInfoToAttributes();
return retVal;
} else if (placer == "heap") {
if (placer == "heap") {
PlacerHeapCfg cfg(getCtx());
cfg.criticalityExponent = 4;
cfg.ioBufTypes.insert(id_TRELLIS_IO);
bool retVal = placer_heap(getCtx(), cfg);
getCtx()->settings[id_place] = 1;
archInfoToAttributes();
return retVal;
cfg.cellGroups.emplace_back();
cfg.cellGroups.back().insert(id_TRELLIS_COMB);
cfg.cellGroups.back().insert(id_TRELLIS_FF);
cfg.cellGroups.back().insert(id_TRELLIS_RAMW);
cfg.placeAllAtOnce = true;
cfg.beta = 0.75;
if (!placer_heap(getCtx(), cfg))
return false;
} else if (placer == "sa") {
if (!placer1(getCtx(), Placer1Cfg(getCtx())))
return false;
} else {
log_error("MachXO2 architecture does not support placer '%s'\n", placer.c_str());
}
getCtx()->settings[id_place] = 1;
archInfoToAttributes();
return true;
}
bool Arch::route()
@ -418,6 +430,8 @@ bool Arch::route()
disable_router_lutperm = getCtx()->setting<bool>("arch.disable_router_lutperm", false);
assignArchInfo();
bool result;
if (router == "router1") {
result = router1(getCtx(), Router1Cfg(getCtx()));

190
machxo2/arch_place.cc Normal file
View File

@ -0,0 +1,190 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 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 "cells.h"
#include "design_utils.h"
#include "log.h"
#include "nextpnr.h"
#include "timing.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
inline NetInfo *port_or_nullptr(const CellInfo *cell, IdString name)
{
auto found = cell->ports.find(name);
if (found == cell->ports.end())
return nullptr;
return found->second.net;
}
bool Arch::slices_compatible(LogicTileStatus *lts) const
{
if (lts == nullptr)
return true;
for (int sl = 0; sl < 4; sl++) {
if (!lts->slices[sl].dirty) {
if (!lts->slices[sl].valid)
return false;
continue;
}
lts->slices[sl].dirty = false;
lts->slices[sl].valid = false;
bool found_ff = false;
uint8_t last_ff_flags = 0;
IdString last_ce_sig;
bool ramw_used = false;
if (sl == 2 && lts->cells[((sl * 2) << lc_idx_shift) | BEL_RAMW] != nullptr)
ramw_used = true;
for (int l = 0; l < 2; l++) {
bool comb_m_used = false;
CellInfo *comb = lts->cells[((sl * 2 + l) << lc_idx_shift) | BEL_COMB];
if (comb != nullptr) {
uint8_t flags = comb->combInfo.flags;
if (ramw_used && !(flags & ArchCellInfo::COMB_RAMW_BLOCK))
return false;
if (flags & ArchCellInfo::COMB_MUX5) {
// MUX5 uses M signal and must be in LC 0
comb_m_used = true;
if (l != 0)
return false;
}
if (flags & ArchCellInfo::COMB_MUX6) {
// MUX6+ uses M signal and must be in LC 1
comb_m_used = true;
if (l != 1)
return false;
if (comb->combInfo.mux_fxad != nullptr &&
(comb->combInfo.mux_fxad->combInfo.flags & ArchCellInfo::COMB_MUX5)) {
// LUT6 structure must be rooted at SLICE 0 or 2
if (sl != 0 && sl != 2)
return false;
}
}
// LUTRAM must be in bottom two SLICEs only
if ((flags & ArchCellInfo::COMB_LUTRAM) && (sl > 1))
return false;
if (l == 1) {
// Carry usage must be the same for LCs 0 and 1 in a SLICE
CellInfo *comb0 = lts->cells[((sl * 2 + 0) << lc_idx_shift) | BEL_COMB];
if (comb0 &&
((comb0->combInfo.flags & ArchCellInfo::COMB_CARRY) != (flags & ArchCellInfo::COMB_CARRY)))
return false;
}
}
CellInfo *ff = lts->cells[((sl * 2 + l) << lc_idx_shift) | BEL_FF];
if (ff != nullptr) {
uint8_t flags = ff->ffInfo.flags;
if (comb_m_used && (flags & ArchCellInfo::FF_M_USED))
return false;
if (found_ff) {
if ((flags & ArchCellInfo::FF_GSREN) != (last_ff_flags & ArchCellInfo::FF_GSREN))
return false;
if ((flags & ArchCellInfo::FF_CECONST) != (last_ff_flags & ArchCellInfo::FF_CECONST))
return false;
if ((flags & ArchCellInfo::FF_CEINV) != (last_ff_flags & ArchCellInfo::FF_CEINV))
return false;
if (ff->ffInfo.ce_sig != last_ce_sig)
return false;
} else {
found_ff = true;
last_ff_flags = flags;
last_ce_sig = ff->ffInfo.ce_sig;
}
}
}
lts->slices[sl].valid = true;
}
if (lts->tile_dirty) {
bool found_global_ff = false;
bool found_global_dpram = false;
bool global_lsrinv = false;
bool global_clkinv = false;
bool global_async = false;
IdString clk_sig, lsr_sig;
lts->tile_dirty = false;
lts->tile_valid = false;
#define CHECK_EQUAL(x, y) \
do { \
if ((x) != (y)) \
return false; \
} while (0)
for (int i = 0; i < 8; i++) {
if (i < 4) {
// DPRAM
CellInfo *comb = lts->cells[(i << lc_idx_shift) | BEL_COMB];
if (comb != nullptr && (comb->combInfo.flags & ArchCellInfo::COMB_LUTRAM)) {
if (found_global_dpram) {
CHECK_EQUAL(bool(comb->combInfo.flags & ArchCellInfo::COMB_RAM_WCKINV), global_clkinv);
CHECK_EQUAL(bool(comb->combInfo.flags & ArchCellInfo::COMB_RAM_WREINV), global_lsrinv);
} else {
global_clkinv = bool(comb->combInfo.flags & ArchCellInfo::COMB_RAM_WCKINV);
global_lsrinv = bool(comb->combInfo.flags & ArchCellInfo::COMB_RAM_WREINV);
found_global_dpram = true;
}
}
}
// FF
CellInfo *ff = lts->cells[(i << lc_idx_shift) | BEL_FF];
if (ff != nullptr) {
if (found_global_dpram) {
CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_CLKINV), global_clkinv);
CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_LSRINV), global_lsrinv);
}
if (found_global_ff) {
CHECK_EQUAL(ff->ffInfo.clk_sig, clk_sig);
CHECK_EQUAL(ff->ffInfo.lsr_sig, lsr_sig);
CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_CLKINV), global_clkinv);
CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_LSRINV), global_lsrinv);
CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_ASYNC), global_async);
} else {
clk_sig = ff->ffInfo.clk_sig;
lsr_sig = ff->ffInfo.lsr_sig;
global_clkinv = bool(ff->ffInfo.flags & ArchCellInfo::FF_CLKINV);
global_lsrinv = bool(ff->ffInfo.flags & ArchCellInfo::FF_LSRINV);
global_async = bool(ff->ffInfo.flags & ArchCellInfo::FF_ASYNC);
found_global_ff = true;
}
}
}
#undef CHECK_EQUAL
lts->tile_valid = true;
} else {
if (!lts->tile_valid)
return false;
}
return true;
}
bool Arch::isBelLocationValid(BelId bel, bool explain_invalid) const
{
IdString bel_type = getBelType(bel);
if (bel_type.in(id_TRELLIS_COMB, id_TRELLIS_FF, id_TRELLIS_RAMW)) {
return slices_compatible(tile_status.at(tile_index(bel)).lts);
}
return true;
}
NEXTPNR_NAMESPACE_END

View File

@ -249,47 +249,6 @@ void dram_to_comb(Context *ctx, CellInfo *ram, CellInfo *comb, CellInfo *ramw, i
comb->attrs[attr.first] = attr.second;
}
void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff)
{
lc->params[id_LUT0_INITVAL] = lut->params[id_INIT];
for (std::string i : {"A", "B", "C", "D"}) {
IdString lut_port = ctx->id(i);
IdString lc_port = ctx->id(i + "0");
lut->movePortTo(lut_port, lc, lc_port);
}
lut->movePortTo(id_Z, lc, id_F0);
}
void dff_to_lc(Context *ctx, CellInfo *dff, CellInfo *lc, LutType lut_type)
{
// FIXME: This will have to change once we support FFs with reset value of 1.
lc->params[id_REG0_REGSET] = std::string("RESET");
dff->movePortTo(id_CLK, lc, id_CLK);
dff->movePortTo(id_LSR, lc, id_LSR);
dff->movePortTo(id_Q, lc, id_Q0);
if (lut_type == LutType::PassThru) {
// If a register's DI port is fed by a constant, options for placing are
// limited. Use the LUT to get around this.
// LUT output will go to F0, which will feed back to DI0 input.
lc->params[id_LUT0_INITVAL] = Property(0xAAAA, 16);
dff->movePortTo(id_DI, lc, id_A0);
lc->connectPorts(id_F0, lc, id_DI0);
} else if (lut_type == LutType::None) {
// If there is no LUT, use the M0 input because DI0 requires
// going through the LUTs.
lc->params[id_REG0_SD] = std::string("0");
dff->movePortTo(id_DI, lc, id_M0);
} else {
// Otherwise, there's a LUT being used in the slice and mapping DI to
// DI0 input is fine.
dff->movePortTo(id_DI, lc, id_DI0);
}
}
void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector<std::unique_ptr<CellInfo>> &created_cells,
pool<IdString> &todelete_cells)
{

View File

@ -25,49 +25,36 @@
NEXTPNR_NAMESPACE_BEGIN
// When packing DFFs, we need context of how it's connected to a LUT to
// properly map DFF ports to TRELLIS_SLICEs; DI0 input muxes F0 and OFX0,
// and a DFF inside a slice can use either DI0 or M0 as an input.
enum class LutType
{
None,
Normal,
PassThru,
};
// Create a MachXO2 arch cell and return it
// Name will be automatically assigned if not specified
std::unique_ptr<CellInfo> create_machxo2_cell(Context *ctx, IdString type, std::string name = "");
// Return true if a cell is a LUT
inline bool is_lut(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_LUT4; }
inline bool is_carry(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_CCU2C; }
inline bool is_dpram(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_DPR16X4; }
inline bool is_trellis_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_IO; }
// Return true if a cell is a flipflop
inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_FF; }
// Convert a LUT primitive to (part of) an GENERIC_SLICE, swapping ports
// as needed. Set no_dff if a DFF is not being used, so that the output
// can be reconnected
void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff = true);
inline bool is_carry(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_CCU2C; }
// Convert a DFF primitive to (part of) an GENERIC_SLICE, setting parameters
// and reconnecting signals as necessary. If pass_thru_lut is True, the LUT will
// be configured as pass through and D connected to I0, otherwise D will be
// ignored
void dff_to_lc(Context *ctx, CellInfo *dff, CellInfo *lc, LutType lut_type = LutType::Normal);
inline bool is_trellis_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_IO; }
// Convert a nextpnr IO buffer to a TRELLIS_IO
void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector<std::unique_ptr<CellInfo>> &created_cells,
pool<IdString> &todelete_cells);
inline bool is_dpram(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_DPR16X4; }
inline bool is_pfumx(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_PFUMX; }
inline bool is_l6mux(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_L6MUX21; }
void lut_to_comb(Context *ctx, CellInfo *lut);
void dram_to_ramw_split(Context *ctx, CellInfo *ram, CellInfo *ramw);
void ccu2_to_comb(Context *ctx, CellInfo *ccu, CellInfo *comb, NetInfo *internal_carry, int i);
void dram_to_comb(Context *ctx, CellInfo *ram, CellInfo *comb, CellInfo *ramw, int index);
// Convert a nextpnr IO buffer to a TRELLIS_IO
void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector<std::unique_ptr<CellInfo>> &created_cells,
pool<IdString> &todelete_cells);
NEXTPNR_NAMESPACE_END

View File

@ -303,6 +303,192 @@ class Ecp5Packer
return false;
}
// Pass to pack LUT5s into a newly created slice
void pack_lut5xs()
{
log_info("Packing LUT5-7s...\n");
// Gets the "COMB1" side of a LUT5, where we pack a LUT[67] into
auto get_comb1_from_lut5 = [&](CellInfo *lut5) {
NetInfo *f1 = lut5->getPort(id_F1);
NPNR_ASSERT(f1 != nullptr);
NPNR_ASSERT(f1->driver.cell != nullptr);
return f1->driver.cell;
};
dict<IdString, std::pair<CellInfo *, CellInfo *>> lut5_roots, lut6_roots, lut7_roots;
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (is_pfumx(ctx, ci)) {
NetInfo *f0 = ci->ports.at(id_BLUT).net;
if (f0 == nullptr)
log_error("PFUMX '%s' has disconnected port 'BLUT'\n", ci->name.c_str(ctx));
NetInfo *f1 = ci->ports.at(id_ALUT).net;
if (f1 == nullptr)
log_error("PFUMX '%s' has disconnected port 'ALUT'\n", ci->name.c_str(ctx));
CellInfo *lut0 =
(f0->driver.cell && f0->driver.cell->type == id_TRELLIS_COMB && f0->driver.port == id_F)
? f0->driver.cell
: nullptr;
CellInfo *lut1 =
(f1->driver.cell && f1->driver.cell->type == id_TRELLIS_COMB && f1->driver.port == id_F)
? f1->driver.cell
: nullptr;
if (lut0 == nullptr || lut0->cluster != ClusterId())
log_error("PFUMX '%s' has BLUT driven by cell other than a LUT\n", ci->name.c_str(ctx));
if (lut1 == nullptr || lut1->cluster != ClusterId())
log_error("PFUMX '%s' has ALUT driven by cell other than a LUT\n", ci->name.c_str(ctx));
lut0->addInput(id_F1);
lut0->addInput(id_M);
lut0->addOutput(id_OFX);
ci->movePortTo(id_Z, lut0, id_OFX);
ci->movePortTo(id_ALUT, lut0, id_F1);
ci->movePortTo(id_C0, lut0, id_M);
ci->disconnectPort(id_BLUT);
lut5_roots[lut0->name] = {lut0, lut1};
packed_cells.insert(ci->name);
}
}
flush_cells();
// Pack LUT6s
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (is_l6mux(ctx, ci)) {
NetInfo *ofx0_0 = ci->ports.at(id_D0).net;
if (ofx0_0 == nullptr)
log_error("L6MUX21 '%s' has disconnected port 'D0'\n", ci->name.c_str(ctx));
NetInfo *ofx0_1 = ci->ports.at(id_D1).net;
if (ofx0_1 == nullptr)
log_error("L6MUX21 '%s' has disconnected port 'D1'\n", ci->name.c_str(ctx));
CellInfo *comb0 = (ofx0_0->driver.cell && ofx0_0->driver.cell->type == id_TRELLIS_COMB &&
ofx0_0->driver.port == id_OFX)
? ofx0_0->driver.cell
: nullptr;
CellInfo *comb1 = (ofx0_1->driver.cell && ofx0_1->driver.cell->type == id_TRELLIS_COMB &&
ofx0_1->driver.port == id_OFX)
? ofx0_1->driver.cell
: nullptr;
if (comb0 == nullptr) {
if (!net_driven_by(ctx, ofx0_0, is_l6mux, id_Z))
log_error("L6MUX21 '%s' has D0 driven by cell other than a SLICE OFX0 but not a LUT7 mux "
"('%s.%s')\n",
ci->name.c_str(ctx), ofx0_0->driver.cell->name.c_str(ctx),
ofx0_0->driver.port.c_str(ctx));
continue;
}
if (lut6_roots.count(comb0->name))
continue;
if (comb1 == nullptr) {
if (!net_driven_by(ctx, ofx0_1, is_l6mux, id_Z))
log_error("L6MUX21 '%s' has D1 driven by cell other than a SLICE OFX0 but not a LUT7 mux "
"('%s.%s')\n",
ci->name.c_str(ctx), ofx0_0->driver.cell->name.c_str(ctx),
ofx0_0->driver.port.c_str(ctx));
continue;
}
if (lut6_roots.count(comb1->name))
continue;
if (ctx->verbose)
log_info(" mux '%s' forms part of a LUT6\n", cell.first.c_str(ctx));
comb0 = get_comb1_from_lut5(comb0);
comb1 = get_comb1_from_lut5(comb1);
comb1->addInput(id_FXA);
comb1->addInput(id_FXB);
comb1->addInput(id_M);
comb1->addOutput(id_OFX);
ci->movePortTo(id_D0, comb1, id_FXA);
ci->movePortTo(id_D1, comb1, id_FXB);
ci->movePortTo(id_SD, comb1, id_M);
ci->movePortTo(id_Z, comb1, id_OFX);
lut6_roots[comb1->name] = {comb0, comb1};
packed_cells.insert(ci->name);
}
}
flush_cells();
// Pack LUT7s
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (is_l6mux(ctx, ci)) {
NetInfo *ofx1_0 = ci->ports.at(id_D0).net;
if (ofx1_0 == nullptr)
log_error("L6MUX21 '%s' has disconnected port 'D0'\n", ci->name.c_str(ctx));
NetInfo *ofx1_1 = ci->ports.at(id_D1).net;
if (ofx1_1 == nullptr)
log_error("L6MUX21 '%s' has disconnected port 'D1'\n", ci->name.c_str(ctx));
CellInfo *comb1 = (ofx1_0->driver.cell && ofx1_0->driver.cell->type == id_TRELLIS_COMB &&
ofx1_0->driver.port == id_OFX)
? ofx1_0->driver.cell
: nullptr;
CellInfo *comb3 = (ofx1_1->driver.cell && ofx1_1->driver.cell->type == id_TRELLIS_COMB &&
ofx1_1->driver.port == id_OFX)
? ofx1_1->driver.cell
: nullptr;
if (comb1 == nullptr)
log_error("L6MUX21 '%s' has D0 driven by cell other than a SLICE OFX ('%s.%s')\n",
ci->name.c_str(ctx), ofx1_0->driver.cell->name.c_str(ctx),
ofx1_0->driver.port.c_str(ctx));
if (comb3 == nullptr)
log_error("L6MUX21 '%s' has D1 driven by cell other than a SLICE OFX ('%s.%s')\n",
ci->name.c_str(ctx), ofx1_1->driver.cell->name.c_str(ctx),
ofx1_1->driver.port.c_str(ctx));
NetInfo *fxa_0 = comb1->ports.at(id_FXA).net;
if (fxa_0 == nullptr)
log_error("SLICE '%s' has disconnected port 'FXA'\n", comb1->name.c_str(ctx));
NetInfo *fxa_1 = comb3->ports.at(id_FXA).net;
if (fxa_1 == nullptr)
log_error("SLICE '%s' has disconnected port 'FXA'\n", comb3->name.c_str(ctx));
CellInfo *comb2 = net_driven_by(
ctx, fxa_1,
[](const Context *ctx, const CellInfo *ci) {
(void)ctx;
return ci->type == id_TRELLIS_COMB;
},
id_OFX);
if (comb2 == nullptr)
log_error("SLICE '%s' has FXA driven by cell other than a SLICE OFX0 ('%s.%s')\n",
comb3->name.c_str(ctx), fxa_1->driver.cell->name.c_str(ctx),
fxa_1->driver.port.c_str(ctx));
comb2 = get_comb1_from_lut5(comb2);
comb2->addInput(id_FXA);
comb2->addInput(id_FXB);
comb2->addInput(id_M);
comb2->addOutput(id_OFX);
ci->movePortTo(id_D0, comb2, id_FXA);
ci->movePortTo(id_D1, comb2, id_FXB);
ci->movePortTo(id_SD, comb2, id_M);
ci->movePortTo(id_Z, comb2, id_OFX);
lut7_roots[comb2->name] = {comb1, comb3};
packed_cells.insert(ci->name);
}
}
for (auto &root : lut7_roots) {
auto &cells = root.second;
cells.second->cluster = cells.second->name;
cells.second->constr_abs_z = true;
cells.second->constr_z = (1 << Arch::lc_idx_shift) | Arch::BEL_COMB;
rel_constr_cells(cells.second, cells.first, (4 << Arch::lc_idx_shift));
}
for (auto &root : lut6_roots) {
auto &cells = root.second;
rel_constr_cells(cells.second, cells.first, (2 << Arch::lc_idx_shift));
}
for (auto &root : lut5_roots) {
auto &cells = root.second;
rel_constr_cells(cells.first, cells.second, (1 << Arch::lc_idx_shift));
}
flush_cells();
}
// Simple "packer" to remove nextpnr IOBUFs, this assumes IOBUFs are manually instantiated
void pack_io()
{
@ -914,6 +1100,7 @@ class Ecp5Packer
pack_dram();
pack_carries();
pack_luts();
pack_lut5xs();
pack_ffs();
ctx->fixupHierarchy();
ctx->check();
@ -1021,166 +1208,4 @@ void Arch::assignArchInfo()
}
}
inline NetInfo *port_or_nullptr(const CellInfo *cell, IdString name)
{
auto found = cell->ports.find(name);
if (found == cell->ports.end())
return nullptr;
return found->second.net;
}
bool Arch::slices_compatible(LogicTileStatus *lts) const
{
if (lts == nullptr)
return true;
for (int sl = 0; sl < 4; sl++) {
if (!lts->slices[sl].dirty) {
if (!lts->slices[sl].valid)
return false;
continue;
}
lts->slices[sl].dirty = false;
lts->slices[sl].valid = false;
bool found_ff = false;
uint8_t last_ff_flags = 0;
IdString last_ce_sig;
bool ramw_used = false;
if (sl == 2 && lts->cells[((sl * 2) << lc_idx_shift) | BEL_RAMW] != nullptr)
ramw_used = true;
for (int l = 0; l < 2; l++) {
bool comb_m_used = false;
CellInfo *comb = lts->cells[((sl * 2 + l) << lc_idx_shift) | BEL_COMB];
if (comb != nullptr) {
uint8_t flags = comb->combInfo.flags;
if (ramw_used && !(flags & ArchCellInfo::COMB_RAMW_BLOCK))
return false;
if (flags & ArchCellInfo::COMB_MUX5) {
// MUX5 uses M signal and must be in LC 0
comb_m_used = true;
if (l != 0)
return false;
}
if (flags & ArchCellInfo::COMB_MUX6) {
// MUX6+ uses M signal and must be in LC 1
comb_m_used = true;
if (l != 1)
return false;
if (comb->combInfo.mux_fxad != nullptr &&
(comb->combInfo.mux_fxad->combInfo.flags & ArchCellInfo::COMB_MUX5)) {
// LUT6 structure must be rooted at SLICE 0 or 2
if (sl != 0 && sl != 2)
return false;
}
}
// LUTRAM must be in bottom two SLICEs only
if ((flags & ArchCellInfo::COMB_LUTRAM) && (sl > 1))
return false;
if (l == 1) {
// Carry usage must be the same for LCs 0 and 1 in a SLICE
CellInfo *comb0 = lts->cells[((sl * 2 + 0) << lc_idx_shift) | BEL_COMB];
if (comb0 &&
((comb0->combInfo.flags & ArchCellInfo::COMB_CARRY) != (flags & ArchCellInfo::COMB_CARRY)))
return false;
}
}
CellInfo *ff = lts->cells[((sl * 2 + l) << lc_idx_shift) | BEL_FF];
if (ff != nullptr) {
uint8_t flags = ff->ffInfo.flags;
if (comb_m_used && (flags & ArchCellInfo::FF_M_USED))
return false;
if (found_ff) {
if ((flags & ArchCellInfo::FF_GSREN) != (last_ff_flags & ArchCellInfo::FF_GSREN))
return false;
if ((flags & ArchCellInfo::FF_CECONST) != (last_ff_flags & ArchCellInfo::FF_CECONST))
return false;
if ((flags & ArchCellInfo::FF_CEINV) != (last_ff_flags & ArchCellInfo::FF_CEINV))
return false;
if (ff->ffInfo.ce_sig != last_ce_sig)
return false;
} else {
found_ff = true;
last_ff_flags = flags;
last_ce_sig = ff->ffInfo.ce_sig;
}
}
}
lts->slices[sl].valid = true;
}
if (lts->tile_dirty) {
bool found_global_ff = false;
bool found_global_dpram = false;
bool global_lsrinv = false;
bool global_clkinv = false;
bool global_async = false;
IdString clk_sig, lsr_sig;
lts->tile_dirty = false;
lts->tile_valid = false;
#define CHECK_EQUAL(x, y) \
do { \
if ((x) != (y)) \
return false; \
} while (0)
for (int i = 0; i < 8; i++) {
if (i < 4) {
// DPRAM
CellInfo *comb = lts->cells[(i << lc_idx_shift) | BEL_COMB];
if (comb != nullptr && (comb->combInfo.flags & ArchCellInfo::COMB_LUTRAM)) {
if (found_global_dpram) {
CHECK_EQUAL(bool(comb->combInfo.flags & ArchCellInfo::COMB_RAM_WCKINV), global_clkinv);
CHECK_EQUAL(bool(comb->combInfo.flags & ArchCellInfo::COMB_RAM_WREINV), global_lsrinv);
} else {
global_clkinv = bool(comb->combInfo.flags & ArchCellInfo::COMB_RAM_WCKINV);
global_lsrinv = bool(comb->combInfo.flags & ArchCellInfo::COMB_RAM_WREINV);
found_global_dpram = true;
}
}
}
// FF
CellInfo *ff = lts->cells[(i << lc_idx_shift) | BEL_FF];
if (ff != nullptr) {
if (found_global_dpram) {
CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_CLKINV), global_clkinv);
CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_LSRINV), global_lsrinv);
}
if (found_global_ff) {
CHECK_EQUAL(ff->ffInfo.clk_sig, clk_sig);
CHECK_EQUAL(ff->ffInfo.lsr_sig, lsr_sig);
CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_CLKINV), global_clkinv);
CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_LSRINV), global_lsrinv);
CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_ASYNC), global_async);
} else {
clk_sig = ff->ffInfo.clk_sig;
lsr_sig = ff->ffInfo.lsr_sig;
global_clkinv = bool(ff->ffInfo.flags & ArchCellInfo::FF_CLKINV);
global_lsrinv = bool(ff->ffInfo.flags & ArchCellInfo::FF_LSRINV);
global_async = bool(ff->ffInfo.flags & ArchCellInfo::FF_ASYNC);
found_global_ff = true;
}
}
}
#undef CHECK_EQUAL
lts->tile_valid = true;
} else {
if (!lts->tile_valid)
return false;
}
return true;
}
bool Arch::isBelLocationValid(BelId bel, bool explain_invalid) const
{
IdString bel_type = getBelType(bel);
if (bel_type.in(id_TRELLIS_COMB, id_TRELLIS_FF, id_TRELLIS_RAMW)) {
return slices_compatible(tile_status.at(tile_index(bel)).lts);
}
return true;
}
NEXTPNR_NAMESPACE_END