aboutsummaryrefslogtreecommitdiffstats
path: root/ecp5
diff options
context:
space:
mode:
authorDavid Shah <davey1576@gmail.com>2019-02-19 14:12:54 +0000
committerDavid Shah <davey1576@gmail.com>2019-02-24 10:28:25 +0100
commit817ba5a4b9cb0b64b2a1170205a23e7a9107a84b (patch)
tree2dc1b807e5f62ecbe2bf9dbc1403f4715c075050 /ecp5
parentfd52db813f2e4c3dd3c6a73a84be56ad9f9a11ea (diff)
downloadnextpnr-817ba5a4b9cb0b64b2a1170205a23e7a9107a84b.tar.gz
nextpnr-817ba5a4b9cb0b64b2a1170205a23e7a9107a84b.tar.bz2
nextpnr-817ba5a4b9cb0b64b2a1170205a23e7a9107a84b.zip
ecp5: Add DELAYF/DELAYG support
Signed-off-by: David Shah <davey1576@gmail.com>
Diffstat (limited to 'ecp5')
-rw-r--r--ecp5/bitstream.cc28
-rw-r--r--ecp5/cells.h11
-rw-r--r--ecp5/pack.cc123
3 files changed, 149 insertions, 13 deletions
diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc
index d425914e..0c3b6b2b 100644
--- a/ecp5/bitstream.cc
+++ b/ecp5/bitstream.cc
@@ -790,18 +790,22 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
cc.tiles[pio_tile].add_enum(pio + ".PULLMODE", str_or_default(ci->attrs, ctx->id("PULLMODE"), "NONE"));
if (ci->attrs.count(ctx->id("TERMINATION"))) {
auto vccio = get_vccio(ioType_from_str(iotype));
- switch(vccio) {
- case IOVoltage::VCC_1V8:
- cc.tiles[pio_tile].add_enum(pio + ".TERMINATION_1V8", str_or_default(ci->attrs, ctx->id("TERMINATION"), "OFF"));
- break;
- case IOVoltage::VCC_1V5:
- cc.tiles[pio_tile].add_enum(pio + ".TERMINATION_1V5", str_or_default(ci->attrs, ctx->id("TERMINATION"), "OFF"));
- break;
- case IOVoltage::VCC_1V35:
- cc.tiles[pio_tile].add_enum(pio + ".TERMINATION_1V35", str_or_default(ci->attrs, ctx->id("TERMINATION"), "OFF"));
- break;
- default:
- log_error("TERMINATION is not supported with Vcc = %s (on PIO %s)\n", iovoltage_to_str(vccio).c_str(), ci->name.c_str(ctx));
+ switch (vccio) {
+ case IOVoltage::VCC_1V8:
+ cc.tiles[pio_tile].add_enum(pio + ".TERMINATION_1V8",
+ str_or_default(ci->attrs, ctx->id("TERMINATION"), "OFF"));
+ break;
+ case IOVoltage::VCC_1V5:
+ cc.tiles[pio_tile].add_enum(pio + ".TERMINATION_1V5",
+ str_or_default(ci->attrs, ctx->id("TERMINATION"), "OFF"));
+ break;
+ case IOVoltage::VCC_1V35:
+ cc.tiles[pio_tile].add_enum(pio + ".TERMINATION_1V35",
+ str_or_default(ci->attrs, ctx->id("TERMINATION"), "OFF"));
+ break;
+ default:
+ log_error("TERMINATION is not supported with Vcc = %s (on PIO %s)\n",
+ iovoltage_to_str(vccio).c_str(), ci->name.c_str(ctx));
}
}
std::string datamux_oddr = str_or_default(ci->params, ctx->id("DATAMUX_ODDR"), "PADDO");
diff --git a/ecp5/cells.h b/ecp5/cells.h
index dcef99e3..e66f8f21 100644
--- a/ecp5/cells.h
+++ b/ecp5/cells.h
@@ -46,6 +46,17 @@ inline bool is_pfumx(const BaseCtx *ctx, const CellInfo *cell) { return cell->ty
inline bool is_l6mux(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("L6MUX21"); }
+inline bool is_iologic_input_cell(const BaseCtx *ctx, const CellInfo *cell)
+{
+ return cell->type == ctx->id("IDDRX1F") || cell->type == ctx->id("IDDRX2F") || cell->type == ctx->id("IDDR71B") ||
+ cell->type == ctx->id("IDDRX2DQA");
+}
+inline bool is_iologic_output_cell(const BaseCtx *ctx, const CellInfo *cell)
+{
+ return cell->type == ctx->id("ODDRX1F") || cell->type == ctx->id("ODDRX2F") || cell->type == ctx->id("ODDR71B") ||
+ cell->type == ctx->id("ODDRX2DQA") || cell->type == ctx->id("ODDRX2DQSB") || cell->type == ctx->id("OSHX2A");
+}
+
void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut);
void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index);
void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc);
diff --git a/ecp5/pack.cc b/ecp5/pack.cc
index 3c838268..1b246ec1 100644
--- a/ecp5/pack.cc
+++ b/ecp5/pack.cc
@@ -1569,6 +1569,32 @@ class Ecp5Packer
}
}
+ int lookup_delay(const std::string &del_mode)
+ {
+ if (del_mode == "USER_DEFINED")
+ return 0;
+ else if (del_mode == "DQS_ALIGNED_X2")
+ return 6;
+ else if (del_mode == "DQS_CMD_CLK")
+ return 9;
+ else if (del_mode == "ECLK_ALIGNED")
+ return 21;
+ else if (del_mode == "ECLK_CENTERED")
+ return 11;
+ else if (del_mode == "ECLKBRIDGE_ALIGNED")
+ return 39;
+ else if (del_mode == "ECLKBRIDGE_CENTERED")
+ return 29;
+ else if (del_mode == "SCLK_ALIGNED")
+ return 50;
+ else if (del_mode == "SCLK_CENTERED")
+ return 39;
+ else if (del_mode == "SCLK_ZEROHOLD")
+ return 59;
+ else
+ log_error("Unsupported DEL_MODE '%s'\n", del_mode.c_str());
+ }
+
// Pack IOLOGIC
void pack_iologic()
{
@@ -1634,7 +1660,7 @@ class Ecp5Packer
auto set_iologic_mode = [&](CellInfo *iol, std::string mode) {
auto &curr_mode = iol->params[ctx->id("MODE")];
- if (curr_mode != "NONE" && curr_mode != mode)
+ if (curr_mode != "NONE" && curr_mode != "IREG_OREG" && curr_mode != mode)
log_error("IOLOGIC '%s' has conflicting modes '%s' and '%s'\n", iol->name.c_str(ctx), curr_mode.c_str(),
mode.c_str());
if (iol->type == id_SIOLOGIC && mode != "IREG_OREG" && mode != "IDDRX1_ODDRX1" && mode != "NONE")
@@ -1707,6 +1733,101 @@ class Ecp5Packer
}
};
+ auto tie_zero = [&](CellInfo *ci, IdString port) {
+ std::unique_ptr<CellInfo> zero_cell{new CellInfo};
+ std::unique_ptr<NetInfo> zero_net{new NetInfo};
+ IdString name = ctx->id(ci->name.str(ctx) + "$zero$" + port.str(ctx));
+ zero_cell->type = ctx->id("GND");
+ zero_cell->name = name;
+ zero_net->name = name;
+ zero_cell->ports[ctx->id("GND")].type = PORT_OUT;
+ connect_port(ctx, zero_net.get(), zero_cell.get(), ctx->id("GND"));
+ ctx->nets[name] = std::move(zero_net);
+ new_cells.push_back(std::move(zero_cell));
+ };
+
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type == ctx->id("DELAYF") || ci->type == ctx->id("DELAYG")) {
+ CellInfo *i_pio = net_driven_by(ctx, ci->ports.at(ctx->id("A")).net, is_trellis_io, id_O);
+ CellInfo *o_pio = net_only_drives(ctx, ci->ports.at(ctx->id("Z")).net, is_trellis_io, id_I, true);
+ CellInfo *iol = nullptr;
+ if (i_pio != nullptr && ci->ports.at(ctx->id("A")).net->users.size() == 1) {
+ iol = create_pio_iologic(i_pio, ci);
+ set_iologic_mode(iol, "IREG_OREG");
+ bool drives_iologic = false;
+ for (auto user : ci->ports.at(ctx->id("Z")).net->users)
+ if (is_iologic_input_cell(ctx, user.cell) && user.port == ctx->id("D"))
+ drives_iologic = true;
+ if (drives_iologic) {
+ // Reconnect to PIO which the packer expects later on
+ NetInfo *input_net = ci->ports.at(ctx->id("A")).net, *dly_net = ci->ports.at(ctx->id("Z")).net;
+ disconnect_port(ctx, i_pio, id_O);
+ i_pio->ports.at(id_O).net = nullptr;
+ disconnect_port(ctx, ci, id_A);
+ ci->ports.at(id_A).net = nullptr;
+ disconnect_port(ctx, ci, id_Z);
+ ci->ports.at(id_Z).net = nullptr;
+ connect_port(ctx, dly_net, i_pio, id_O);
+ connect_port(ctx, input_net, iol, id_INDD);
+ connect_port(ctx, input_net, iol, id_DI);
+ } else {
+ replace_port(ci, id_A, iol, id_PADDI);
+ replace_port(ci, id_Z, iol, id_INDD);
+ }
+ packed_cells.insert(cell.first);
+ } else if (o_pio != nullptr) {
+ iol = create_pio_iologic(o_pio, ci);
+ iol->params[ctx->id("DELAY.OUTDEL")] = "ENABLED";
+ bool driven_by_iol = false;
+ NetInfo *input_net = ci->ports.at(ctx->id("A")).net, *dly_net = ci->ports.at(ctx->id("Z")).net;
+ if (input_net->driver.cell != nullptr && is_iologic_output_cell(ctx, input_net->driver.cell) &&
+ input_net->driver.port == ctx->id("Q"))
+ driven_by_iol = true;
+ if (driven_by_iol) {
+ disconnect_port(ctx, o_pio, id_I);
+ i_pio->ports.at(id_I).net = nullptr;
+ disconnect_port(ctx, ci, id_A);
+ ci->ports.at(id_A).net = nullptr;
+ disconnect_port(ctx, ci, id_Z);
+ ci->ports.at(id_Z).net = nullptr;
+ connect_port(ctx, input_net, o_pio, id_I);
+ ctx->nets.erase(dly_net->name);
+ } else {
+ replace_port(ci, ctx->id("A"), iol, id_TXDATA0);
+ replace_port(ci, ctx->id("Z"), iol, id_IOLDO);
+ if (!o_pio->ports.count(id_IOLDO)) {
+ o_pio->ports[id_IOLDO].name = id_IOLDO;
+ o_pio->ports[id_IOLDO].type = PORT_IN;
+ }
+ replace_port(o_pio, id_I, o_pio, id_IOLDO);
+ }
+ packed_cells.insert(cell.first);
+ } else {
+ log_error("%s '%s' must be connected directly to top level input or output\n", ci->type.c_str(ctx),
+ ci->name.c_str(ctx));
+ }
+ iol->params[ctx->id("DELAY.DEL_VALUE")] =
+ std::to_string(lookup_delay(str_or_default(ci->params, ctx->id("DEL_MODE"), "USER_DEFINED")));
+ if (ci->params.count(ctx->id("DEL_VALUE")))
+ iol->params[ctx->id("DELAY.DEL_VALUE")] = ci->params.at(ctx->id("DEL_VALUE"));
+ if (ci->ports.count(id_LOADN))
+ replace_port(ci, id_LOADN, iol, id_LOADN);
+ else
+ tie_zero(ci, id_LOADN);
+ if (ci->ports.count(id_MOVE))
+ replace_port(ci, id_MOVE, iol, id_MOVE);
+ else
+ tie_zero(ci, id_MOVE);
+ if (ci->ports.count(id_DIRECTION))
+ replace_port(ci, id_DIRECTION, iol, id_DIRECTION);
+ else
+ tie_zero(ci, id_DIRECTION);
+ if (ci->ports.count(id_CFLAG))
+ replace_port(ci, id_CFLAG, iol, id_CFLAG);
+ }
+ }
+
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ci->type == ctx->id("IDDRX1F")) {