Added automatic inference and integration of FFs driving T pin into IOLOGIC
Signed-off-by: Maciej Kurc <mkurc@antmicro.com>
This commit is contained in:
parent
6948d41616
commit
ef9eee6b15
@ -568,8 +568,14 @@ struct NexusFasmWriter
|
|||||||
write_enum(cell, "MODE");
|
write_enum(cell, "MODE");
|
||||||
write_enum(cell, "IDDRX1_ODDRX1.OUTPUT");
|
write_enum(cell, "IDDRX1_ODDRX1.OUTPUT");
|
||||||
write_enum(cell, "GSR", "DISABLED");
|
write_enum(cell, "GSR", "DISABLED");
|
||||||
|
write_enum(cell, "TSREG.REGSET", "RESET");
|
||||||
write_cell_muxes(cell);
|
write_cell_muxes(cell);
|
||||||
pop();
|
pop();
|
||||||
|
|
||||||
|
// FIXME: Workaround for unknown bits
|
||||||
|
push_tile(bel.tile);
|
||||||
|
write_enum(cell, "UNKNOWN");
|
||||||
|
pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write config for some kind of DSP cell
|
// Write config for some kind of DSP cell
|
||||||
|
187
nexus/pack.cc
187
nexus/pack.cc
@ -2075,57 +2075,140 @@ struct NexusPacker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finds and returns a flip-flop that drives the given port of an IOB cell
|
||||||
|
// If an associated IOLOGIC cell is provided then checks whether the
|
||||||
|
// flip-flop matches its clock and reset.
|
||||||
|
CellInfo* get_ff_for_iob (CellInfo* iob, IdString port, CellInfo* iol) {
|
||||||
|
|
||||||
|
// Get the net
|
||||||
|
NetInfo* net = get_net_or_empty(iob, port);
|
||||||
|
if (net == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the flip-flop that drives it
|
||||||
|
CellInfo* ff = net->driver.cell;
|
||||||
|
if (ff->type != id_OXIDE_FF) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get clock nets of IOLOGIC and the flip-flop
|
||||||
|
if (iol != nullptr) {
|
||||||
|
NetInfo* iol_c = get_net_or_empty(iol, id_SCLKOUT);
|
||||||
|
NetInfo* ff_c = get_net_or_empty(ff, id_CLK);
|
||||||
|
|
||||||
|
// If one of them is floating or it is not the same net then abort
|
||||||
|
if (iol_c == nullptr || ff_c == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (iol_c->name != ff_c->name) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get reset nets of IOLOGIC and the flip-flop
|
||||||
|
if (iol != nullptr) {
|
||||||
|
NetInfo* iol_r = get_net_or_empty(iol, id_LSROUT);
|
||||||
|
NetInfo* ff_r = get_net_or_empty(ff, id_LSR);
|
||||||
|
|
||||||
|
// If one of them is floating or it is not the same net then abort.
|
||||||
|
// But both can be floating.
|
||||||
|
if (!(iol_r == nullptr && ff_r == nullptr)) {
|
||||||
|
if (iol_r == nullptr || ff_r == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (iol_r->name != ff_r->name) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Check if the flip-flop has:
|
||||||
|
// - non-inverted clock
|
||||||
|
// - same reset "type" as ODDR
|
||||||
|
// - others ?
|
||||||
|
|
||||||
|
return ff;
|
||||||
|
}
|
||||||
|
|
||||||
// IOLOGIC requires some special handling around itself and IOB. This
|
// IOLOGIC requires some special handling around itself and IOB. This
|
||||||
// function does that.
|
// function does that.
|
||||||
void handle_iologic() {
|
void handle_iologic() {
|
||||||
|
|
||||||
|
// Map of flip-flop cells that drive IOLOGIC+IOB pairs
|
||||||
|
dict<IdString, std::vector<std::pair<IdString,IdString>>> tff_map;
|
||||||
|
|
||||||
for (auto &cell : ctx->cells) {
|
for (auto &cell : ctx->cells) {
|
||||||
CellInfo* iol = cell.second.get();
|
CellInfo* iol = cell.second.get();
|
||||||
if (iol->type != id_SIOLOGIC && iol->type != id_IOLOGIC) {
|
if (iol->type != id_SIOLOGIC && iol->type != id_IOLOGIC) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isIDDR = false;
|
||||||
|
bool isODDR = false;
|
||||||
|
|
||||||
CellInfo *iob = nullptr;
|
CellInfo *iob = nullptr;
|
||||||
NetInfo *di = get_net_or_empty(iol, id_DI);
|
NetInfo *di = get_net_or_empty(iol, id_DI);
|
||||||
if (di != nullptr && di->driver.cell != nullptr)
|
if (di != nullptr && di->driver.cell != nullptr) {
|
||||||
iob = di->driver.cell;
|
iob = di->driver.cell;
|
||||||
|
isIDDR = true;
|
||||||
|
}
|
||||||
NetInfo *dout = get_net_or_empty(iol, id_DOUT);
|
NetInfo *dout = get_net_or_empty(iol, id_DOUT);
|
||||||
if (dout != nullptr && dout->users.size() == 1)
|
if (dout != nullptr && dout->users.size() == 1) {
|
||||||
iob = dout->users.at(0).cell;
|
iob = dout->users.at(0).cell;
|
||||||
|
isODDR = true;
|
||||||
|
}
|
||||||
NetInfo *tout = get_net_or_empty(iol, id_TOUT);
|
NetInfo *tout = get_net_or_empty(iol, id_TOUT);
|
||||||
if (tout != nullptr && tout->users.size() == 1)
|
if (tout != nullptr && tout->users.size() == 1) {
|
||||||
iob = tout->users.at(0).cell;
|
iob = tout->users.at(0).cell;
|
||||||
|
isODDR = true; // FIXME: Not sure
|
||||||
|
}
|
||||||
NPNR_ASSERT(iob != nullptr);
|
NPNR_ASSERT(iob != nullptr);
|
||||||
|
|
||||||
// SIOLOGIC handling
|
// SIOLOGIC handling
|
||||||
if (iol->type == id_SIOLOGIC) {
|
if (iol->type == id_SIOLOGIC) {
|
||||||
|
|
||||||
// Enable glitch filter for when it uses IDDR as observed
|
// We have IDDR+ODDR
|
||||||
// done by the vendor toolchain.
|
if (isODDR && isIDDR) {
|
||||||
NetInfo* dout = get_net_or_empty(iol, id_DOUT);
|
if (!iob->attrs.count(ctx->id("GLITCHFILTER"))) {
|
||||||
if (dout != nullptr && dout->users.size() == 1) {
|
iob->attrs[ctx->id("GLITCHFILTER")] = std::string("ON");
|
||||||
if (iol->params.count(id_MODE) && iol->params.at(id_MODE).as_string() == "IDDRX1_ODDRX1") {
|
}
|
||||||
|
if (!iob->attrs.count(ctx->id("CLAMP"))) {
|
||||||
if (!iob->attrs.count(ctx->id("GLITCHFILTER"))) {
|
iob->attrs[ctx->id("CLAMP")] = std::string("ON");
|
||||||
iob->attrs[ctx->id("GLITCHFILTER")] = std::string("ON");
|
}
|
||||||
}
|
if (!iob->attrs.count(ctx->id("PULLMODE"))) {
|
||||||
|
iob->attrs[ctx->id("PULLMODE")] = std::string("DOWN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We have ODDR only
|
||||||
|
else if (isODDR && !isIDDR) {
|
||||||
|
if (!iob->attrs.count(ctx->id("GLITCHFILTER"))) {
|
||||||
|
iob->attrs[ctx->id("GLITCHFILTER")] = std::string("OFF");
|
||||||
|
}
|
||||||
|
if (!iob->attrs.count(ctx->id("CLAMP"))) {
|
||||||
|
iob->attrs[ctx->id("CLAMP")] = std::string("OFF");
|
||||||
|
}
|
||||||
|
if (!iob->attrs.count(ctx->id("PULLMODE"))) {
|
||||||
|
iob->attrs[ctx->id("PULLMODE")] = std::string("NONE");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect case when SEIO33_CORE.T is not driven by
|
// Detect case when SEIO33_CORE.T is not driven by
|
||||||
// SIOLOGIC.TOUT. In this case connect SIOLOGIC.TSDATA0 to the
|
// SIOLOGIC.TOUT. In this case connect SIOLOGIC.TSDATA0 to the
|
||||||
// same ned as SEIO33_CORE.I.
|
// same ned as SEIO33_CORE.I.
|
||||||
|
//
|
||||||
|
//
|
||||||
NetInfo* iob_t = get_net_or_empty(iob, id_T);
|
NetInfo* iob_t = get_net_or_empty(iob, id_T);
|
||||||
if (iob_t != nullptr) {
|
if (iob_t != nullptr && isODDR) {
|
||||||
NetInfo* iol_t = get_net_or_empty(iol, id_TOUT);
|
NetInfo* iol_t = get_net_or_empty(iol, id_TOUT);
|
||||||
|
|
||||||
// SIOLOGIC.TOUT is not driving SEIO33_CORE.I
|
// SIOLOGIC.TOUT is not driving SEIO33_CORE.T
|
||||||
if ((iol_t == nullptr) ||
|
if ((iol_t == nullptr) ||
|
||||||
(iol_t != nullptr && iol_t->users.empty()) ||
|
(iol_t != nullptr && iol_t->users.empty()) ||
|
||||||
(iol_t != nullptr && !iol_t->users.empty() && iol_t->name != iob_t->name)) {
|
(iol_t != nullptr && !iol_t->users.empty() && iol_t->name != iob_t->name)) {
|
||||||
|
|
||||||
// In this case if SIOLOGIC.TSDATA0 is not connected
|
// In this case if SIOLOGIC.TSDATA0 is not connected
|
||||||
// to the same net as SEIO33_CORE.I and is not
|
// to the same net as SEIO33_CORE.T and is not
|
||||||
// floating then that configuration is illegal.
|
// floating then that configuration is illegal.
|
||||||
NetInfo* iol_ti = get_net_or_empty(iol, id_TSDATA0);
|
NetInfo* iol_ti = get_net_or_empty(iol, id_TSDATA0);
|
||||||
if (iol_ti != nullptr && (iol_ti->name != iob_t->name)
|
if (iol_ti != nullptr && (iol_ti->name != iob_t->name)
|
||||||
@ -2147,10 +2230,82 @@ struct NexusPacker
|
|||||||
if (ctx->debug) {
|
if (ctx->debug) {
|
||||||
log_info("Reconnecting %s.TSDATA0 to %s\n", ctx->nameOf(iol), ctx->nameOf(iob_t));
|
log_info("Reconnecting %s.TSDATA0 to %s\n", ctx->nameOf(iol), ctx->nameOf(iob_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the T input is driven by a flip-flop. Store
|
||||||
|
// in the map.
|
||||||
|
CellInfo* ff = get_ff_for_iob(iob, id_T, iol);
|
||||||
|
if (ff != nullptr) {
|
||||||
|
tff_map[ff->name].push_back(std::make_pair(
|
||||||
|
iol->name, iob->name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Integrate flip-flops that drive T with IOLOGIC
|
||||||
|
for (auto& it : tff_map) {
|
||||||
|
CellInfo* ff = ctx->cells.at(it.first).get();
|
||||||
|
|
||||||
|
NetInfo* ff_d = get_net_or_empty(ff, id_M); // FIXME: id_D or id_M ?!
|
||||||
|
NPNR_ASSERT(ff_d != nullptr);
|
||||||
|
|
||||||
|
NetInfo* ff_q = get_net_or_empty(ff, id_Q);
|
||||||
|
NPNR_ASSERT(ff_q != nullptr);
|
||||||
|
|
||||||
|
log_info("FF '%s'\n", ctx->nameOf(ff));
|
||||||
|
for (auto& it : ff->params) {
|
||||||
|
log_info(" '%s'='%s'\n", it.first.c_str(ctx), it.second.as_string().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& ios : it.second) {
|
||||||
|
CellInfo* iol = ctx->cells.at(ios.first).get();
|
||||||
|
CellInfo* iob = ctx->cells.at(ios.second).get();
|
||||||
|
|
||||||
|
log_info("Integrating %s into %s\n", ctx->nameOf(ff), ctx->nameOf(iol));
|
||||||
|
|
||||||
|
// Disconnect "old" T net
|
||||||
|
disconnect_port(ctx, iol, id_TSDATA0);
|
||||||
|
disconnect_port(ctx, iob, id_T);
|
||||||
|
|
||||||
|
// Connect the "new" one
|
||||||
|
connect_port(ctx, ff_d, iol, id_TSDATA0);
|
||||||
|
connect_port(ctx, ff_d, iob, id_T);
|
||||||
|
|
||||||
|
// Propagate parameters
|
||||||
|
iol->params[id_SRMODE] = ff->params.at(id_SRMODE);
|
||||||
|
iol->params[id_REGSET] = ff->params.at(id_REGSET);
|
||||||
|
|
||||||
|
iol->params[ctx->id("TSREG.REGSET")] = std::string("SET");
|
||||||
|
|
||||||
|
// CEOUTMUX.1
|
||||||
|
iol->params[ctx->id("CEOUTMUX")] = std::string("1");
|
||||||
|
|
||||||
|
// FIXME: Workaround for an unknown bit
|
||||||
|
IdStringList belName = IdStringList::parse(ctx, iob->attrs[id_BEL].as_string());
|
||||||
|
if (belName[2] == ctx->id("PIOA")) {
|
||||||
|
// UNKNOWN.22.1 for A
|
||||||
|
iol->params[ctx->id("UNKNOWN")] = std::string("22.1");
|
||||||
|
}
|
||||||
|
else if (belName[2] == ctx->id("PIOB")) {
|
||||||
|
// UNKNOWN.77.1 for B
|
||||||
|
iol->params[ctx->id("UNKNOWN")] = std::string("77.1");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log_error("Unknown IO BEL type '%s'\n", belName[2].c_str(ctx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect the flip-flop
|
||||||
|
for (auto& port : ff->ports) {
|
||||||
|
disconnect_port(ctx, ff, port.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the flip-flop
|
||||||
|
ctx->cells.erase(ff->name);
|
||||||
|
// Remove its output net
|
||||||
|
ctx->nets.erase(ff_q->name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit NexusPacker(Context *ctx) : ctx(ctx) {}
|
explicit NexusPacker(Context *ctx) : ctx(ctx) {}
|
||||||
@ -2171,10 +2326,10 @@ struct NexusPacker
|
|||||||
pack_constants();
|
pack_constants();
|
||||||
pack_luts();
|
pack_luts();
|
||||||
pack_ip();
|
pack_ip();
|
||||||
|
handle_iologic();
|
||||||
promote_globals();
|
promote_globals();
|
||||||
place_globals();
|
place_globals();
|
||||||
generate_constraints();
|
generate_constraints();
|
||||||
handle_iologic();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user