More of making it inline
This commit is contained in:
parent
ca3d32e5ac
commit
153144022f
@ -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
190
machxo2/arch_place.cc
Normal 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
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
||||
|
349
machxo2/pack.cc
349
machxo2/pack.cc
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user