Merge pull request #181 from YosysHQ/ecp5_iologic
ecp5: Adding DDR input/output support
This commit is contained in:
commit
0a494fa66c
20
ecp5/arch.cc
20
ecp5/arch.cc
@ -579,6 +579,8 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
|
||||
return false;
|
||||
} else if (cell->type == id_DP16KD) {
|
||||
return false;
|
||||
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -669,6 +671,16 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
|
||||
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
|
||||
}
|
||||
return TMG_IGNORE;
|
||||
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
|
||||
if (port == id_CLK || port == id_ECLK) {
|
||||
return TMG_CLOCK_INPUT;
|
||||
} else if (port == id_IOLDO || port == id_IOLDOI || port == id_IOLDOD || port == id_IOLTO || port == id_PADDI ||
|
||||
port == id_DQSR90 || port == id_DQSW || port == id_DQSW270) {
|
||||
return TMG_IGNORE;
|
||||
} else {
|
||||
clockInfoCount = 1;
|
||||
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
|
||||
}
|
||||
} else {
|
||||
log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this),
|
||||
cell->name.c_str(this));
|
||||
@ -744,6 +756,14 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port
|
||||
info.setup = getDelayFromNS(1);
|
||||
info.hold = getDelayFromNS(0);
|
||||
}
|
||||
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
|
||||
info.clock_port = id_CLK;
|
||||
if (cell->ports.at(port).type == PORT_OUT) {
|
||||
info.clockToQ = getDelayFromNS(0.5);
|
||||
} else {
|
||||
info.setup = getDelayFromNS(0.1);
|
||||
info.hold = getDelayFromNS(0);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
@ -745,6 +745,12 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
|
||||
}
|
||||
if (ci->attrs.count(ctx->id("SLEWRATE")))
|
||||
cc.tiles[pio_tile].add_enum(pio + ".SLEWRATE", str_or_default(ci->attrs, ctx->id("SLEWRATE"), "SLOW"));
|
||||
std::string datamux_oddr = str_or_default(ci->params, ctx->id("DATAMUX_ODDR"), "PADDO");
|
||||
if (datamux_oddr != "PADDO")
|
||||
cc.tiles[pic_tile].add_enum(pio + ".DATAMUX_ODDR", datamux_oddr);
|
||||
std::string datamux_mddr = str_or_default(ci->params, ctx->id("DATAMUX_MDDR"), "PADDO");
|
||||
if (datamux_mddr != "PADDO")
|
||||
cc.tiles[pic_tile].add_enum(pio + ".DATAMUX_MDDR", datamux_mddr);
|
||||
} else if (ci->type == ctx->id("DCCA")) {
|
||||
// Nothing to do
|
||||
} else if (ci->type == ctx->id("DP16KD")) {
|
||||
@ -1078,6 +1084,18 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
|
||||
int_to_bitvector(int_or_default(ci->attrs, ctx->id("MFG_ENABLE_FILTEROPAMP"), 0), 1));
|
||||
|
||||
cc.tilegroups.push_back(tg);
|
||||
} else if (ci->type == id_IOLOGIC || ci->type == id_SIOLOGIC) {
|
||||
Loc pio_loc = ctx->getBelLocation(ci->bel);
|
||||
pio_loc.z -= ci->type == id_SIOLOGIC ? 2 : 4;
|
||||
std::string pic_tile = get_pic_tile(ctx, ctx->getBelByLocation(pio_loc));
|
||||
std::string prim = std::string("IOLOGIC") + "ABCD"[pio_loc.z];
|
||||
for (auto ¶m : ci->params) {
|
||||
if (param.first == ctx->id("DELAY.DEL_VALUE"))
|
||||
cc.tiles[pic_tile].add_word(prim + "." + param.first.str(ctx),
|
||||
int_to_bitvector(std::stoi(param.second), 7));
|
||||
else
|
||||
cc.tiles[pic_tile].add_enum(prim + "." + param.first.str(ctx), param.second);
|
||||
}
|
||||
} else if (ci->type == id_DCUA) {
|
||||
TileGroup tg;
|
||||
tg.tiles = get_dcu_tiles(ctx, ci->bel);
|
||||
|
@ -41,6 +41,22 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str
|
||||
new_cell->name = ctx->id(name);
|
||||
}
|
||||
new_cell->type = type;
|
||||
|
||||
auto copy_bel_ports = [&]() {
|
||||
// First find a Bel of the target type
|
||||
BelId tgt;
|
||||
for (auto bel : ctx->getBels()) {
|
||||
if (ctx->getBelType(bel) == type) {
|
||||
tgt = bel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
NPNR_ASSERT(tgt != BelId());
|
||||
for (auto port : ctx->getBelPins(tgt)) {
|
||||
add_port(ctx, new_cell.get(), port.str(ctx), ctx->getBelPinType(tgt, port));
|
||||
}
|
||||
};
|
||||
|
||||
if (type == ctx->id("TRELLIS_SLICE")) {
|
||||
new_cell->params[ctx->id("MODE")] = "LOGIC";
|
||||
new_cell->params[ctx->id("GSR")] = "DISABLED";
|
||||
@ -111,11 +127,17 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str
|
||||
} else if (type == ctx->id("TRELLIS_IO")) {
|
||||
new_cell->params[ctx->id("DIR")] = "INPUT";
|
||||
new_cell->attrs[ctx->id("IO_TYPE")] = "LVCMOS33";
|
||||
new_cell->params[ctx->id("DATAMUX_ODDR")] = "PADDO";
|
||||
new_cell->params[ctx->id("DATAMUX_MDDR")] = "PADDO";
|
||||
|
||||
add_port(ctx, new_cell.get(), "B", PORT_INOUT);
|
||||
add_port(ctx, new_cell.get(), "I", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "T", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "O", PORT_OUT);
|
||||
|
||||
add_port(ctx, new_cell.get(), "IOLDO", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "IOLTO", PORT_IN);
|
||||
|
||||
} else if (type == ctx->id("LUT4")) {
|
||||
new_cell->params[ctx->id("INIT")] = "0";
|
||||
|
||||
@ -150,6 +172,35 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str
|
||||
add_port(ctx, new_cell.get(), "CLKI", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "CLKO", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "CE", PORT_IN);
|
||||
} else if (type == id_IOLOGIC || type == id_SIOLOGIC) {
|
||||
new_cell->params[ctx->id("MODE")] = "NONE";
|
||||
new_cell->params[ctx->id("GSR")] = "DISABLED";
|
||||
new_cell->params[ctx->id("CLKIMUX")] = "CLK";
|
||||
new_cell->params[ctx->id("CLKOMUX")] = "CLK";
|
||||
new_cell->params[ctx->id("LSRIMUX")] = "0";
|
||||
new_cell->params[ctx->id("LSROMUX")] = "0";
|
||||
new_cell->params[ctx->id("LSRMUX")] = "LSR";
|
||||
|
||||
new_cell->params[ctx->id("DELAY.OUTDEL")] = "DISABLED";
|
||||
new_cell->params[ctx->id("DELAY.DEL_VALUE")] = "0";
|
||||
new_cell->params[ctx->id("DELAY.WAIT_FOR_EDGE")] = "DISABLED";
|
||||
|
||||
if (type == id_IOLOGIC) {
|
||||
new_cell->params[ctx->id("IDDRXN.MODE")] = "NONE";
|
||||
new_cell->params[ctx->id("ODDRXN.MODE")] = "NONE";
|
||||
|
||||
new_cell->params[ctx->id("MIDDRX.MODE")] = "NONE";
|
||||
new_cell->params[ctx->id("MODDRX.MODE")] = "NONE";
|
||||
new_cell->params[ctx->id("MTDDRX.MODE")] = "NONE";
|
||||
|
||||
new_cell->params[ctx->id("IOLTOMUX")] = "NONE";
|
||||
new_cell->params[ctx->id("MTDDRX.DQSW_INVERT")] = "DISABLED";
|
||||
new_cell->params[ctx->id("MTDDRX.REGSET")] = "RESET";
|
||||
|
||||
new_cell->params[ctx->id("MIDDRX_MODDRX.WRCLKMUX")] = "NONE";
|
||||
}
|
||||
// Just copy ports from the Bel
|
||||
copy_bel_ports();
|
||||
} else {
|
||||
log_error("unable to create ECP5 cell of type %s", type.c_str(ctx));
|
||||
}
|
||||
|
@ -1142,3 +1142,43 @@ X(PAD)
|
||||
X(PADDI)
|
||||
X(PADDO)
|
||||
X(PADDT)
|
||||
|
||||
X(IOLOGIC)
|
||||
X(SIOLOGIC)
|
||||
X(DI)
|
||||
X(IOLDO)
|
||||
X(IOLDOD)
|
||||
X(IOLDOI)
|
||||
X(IOLTO)
|
||||
X(INDD)
|
||||
X(LOADN)
|
||||
X(MOVE)
|
||||
X(DIRECTION)
|
||||
X(TSDATA0)
|
||||
X(TXDATA0)
|
||||
X(TXDATA1)
|
||||
X(RXDATA0)
|
||||
X(RXDATA1)
|
||||
X(INFF)
|
||||
X(CFLAG)
|
||||
X(ECLK)
|
||||
X(TSDATA1)
|
||||
X(TXDATA2)
|
||||
X(TXDATA3)
|
||||
X(RXDATA2)
|
||||
X(RXDATA3)
|
||||
X(TXDATA4)
|
||||
X(TXDATA5)
|
||||
X(TXDATA6)
|
||||
X(RXDATA4)
|
||||
X(RXDATA5)
|
||||
X(RXDATA6)
|
||||
X(DQSR90)
|
||||
X(DQSW270)
|
||||
X(DQSW)
|
||||
X(RDPNTR0)
|
||||
X(RDPNTR1)
|
||||
X(RDPNTR2)
|
||||
X(WRPNTR0)
|
||||
X(WRPNTR1)
|
||||
X(WRPNTR2)
|
||||
|
@ -58,6 +58,8 @@ class Ecp5GlobalRouter
|
||||
if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK ||
|
||||
user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK))
|
||||
return true;
|
||||
if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && user.port == id_CLK)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
134
ecp5/pack.cc
134
ecp5/pack.cc
@ -1377,10 +1377,144 @@ class Ecp5Packer
|
||||
}
|
||||
}
|
||||
|
||||
// Check if two nets have identical constant drivers
|
||||
bool equal_constant(NetInfo *a, NetInfo *b)
|
||||
{
|
||||
if (a->driver.cell == nullptr || b->driver.cell == nullptr)
|
||||
return (a->driver.cell == nullptr && b->driver.cell == nullptr);
|
||||
if (a->driver.cell->type != ctx->id("GND") && a->driver.cell->type != ctx->id("VCC"))
|
||||
return false;
|
||||
return a->driver.cell->type == b->driver.cell->type;
|
||||
}
|
||||
|
||||
// Pack IOLOGIC
|
||||
void pack_iologic()
|
||||
{
|
||||
std::unordered_map<IdString, CellInfo *> pio_iologic;
|
||||
|
||||
auto set_iologic_sclk = [&](CellInfo *iol, CellInfo *prim, IdString port, bool input) {
|
||||
NetInfo *sclk = nullptr;
|
||||
if (prim->ports.count(port))
|
||||
sclk = prim->ports[port].net;
|
||||
if (sclk == nullptr) {
|
||||
iol->params[input ? ctx->id("CLKIMUX") : ctx->id("CLKOMUX")] = "0";
|
||||
} else {
|
||||
iol->params[input ? ctx->id("CLKIMUX") : ctx->id("CLKOMUX")] = "CLK";
|
||||
if (iol->ports[id_CLK].net != nullptr) {
|
||||
if (iol->ports[id_CLK].net != sclk && !equal_constant(iol->ports[id_CLK].net, sclk))
|
||||
log_error("IOLOGIC '%s' has conflicting clocks '%s' and '%s'\n", iol->name.c_str(ctx),
|
||||
iol->ports[id_CLK].net->name.c_str(ctx), sclk->name.c_str(ctx));
|
||||
} else {
|
||||
connect_port(ctx, sclk, iol, id_CLK);
|
||||
}
|
||||
}
|
||||
if (prim->ports.count(port))
|
||||
disconnect_port(ctx, prim, port);
|
||||
};
|
||||
|
||||
auto set_iologic_lsr = [&](CellInfo *iol, CellInfo *prim, IdString port, bool input) {
|
||||
NetInfo *lsr = nullptr;
|
||||
if (prim->ports.count(port))
|
||||
lsr = prim->ports[port].net;
|
||||
if (lsr == nullptr) {
|
||||
iol->params[input ? ctx->id("LSRIMUX") : ctx->id("LSROMUX")] = "0";
|
||||
} else {
|
||||
iol->params[input ? ctx->id("LSRIMUX") : ctx->id("LSROMUX")] = "LSRMUX";
|
||||
if (iol->ports[id_LSR].net != nullptr && !equal_constant(iol->ports[id_LSR].net, lsr)) {
|
||||
if (iol->ports[id_LSR].net != lsr)
|
||||
log_error("IOLOGIC '%s' has conflicting LSR signals '%s' and '%s'\n", iol->name.c_str(ctx),
|
||||
iol->ports[id_LSR].net->name.c_str(ctx), lsr->name.c_str(ctx));
|
||||
} else {
|
||||
connect_port(ctx, lsr, iol, id_LSR);
|
||||
}
|
||||
}
|
||||
if (prim->ports.count(port))
|
||||
disconnect_port(ctx, prim, port);
|
||||
};
|
||||
|
||||
auto set_iologic_mode = [&](CellInfo *iol, std::string mode) {
|
||||
auto &curr_mode = iol->params[ctx->id("MODE")];
|
||||
if (curr_mode != "NONE" && 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());
|
||||
curr_mode = mode;
|
||||
};
|
||||
|
||||
auto create_pio_iologic = [&](CellInfo *pio, CellInfo *curr) {
|
||||
if (!pio->attrs.count(ctx->id("BEL")))
|
||||
log_error("IOLOGIC functionality (DDR, DELAY, DQS, etc) can only be used with pin-constrained PIO "
|
||||
"(while processing '%s').\n",
|
||||
curr->name.c_str(ctx));
|
||||
BelId bel = ctx->getBelByName(ctx->id(pio->attrs.at(ctx->id("BEL"))));
|
||||
NPNR_ASSERT(bel != BelId());
|
||||
log_info("IOLOGIC component %s connected to PIO Bel %s\n", curr->name.c_str(ctx),
|
||||
ctx->getBelName(bel).c_str(ctx));
|
||||
Loc loc = ctx->getBelLocation(bel);
|
||||
bool s = false;
|
||||
if (loc.y == 0 || loc.y == (ctx->chip_info->height - 1))
|
||||
s = true;
|
||||
std::unique_ptr<CellInfo> iol =
|
||||
create_ecp5_cell(ctx, s ? id_SIOLOGIC : id_IOLOGIC, pio->name.str(ctx) + "$IOL");
|
||||
|
||||
loc.z += s ? 2 : 4;
|
||||
iol->attrs[ctx->id("BEL")] = ctx->getBelName(ctx->getBelByLocation(loc)).str(ctx);
|
||||
|
||||
CellInfo *iol_ptr = iol.get();
|
||||
pio_iologic[pio->name] = iol_ptr;
|
||||
new_cells.push_back(std::move(iol));
|
||||
return iol_ptr;
|
||||
};
|
||||
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (ci->type == ctx->id("IDDRX1F")) {
|
||||
CellInfo *pio = net_driven_by(ctx, ci->ports.at(ctx->id("D")).net, is_trellis_io, id_O);
|
||||
if (pio == nullptr || ci->ports.at(ctx->id("D")).net->users.size() > 1)
|
||||
log_error("IDDRX1F '%s' D input must be connected only to a top level input\n",
|
||||
ci->name.c_str(ctx));
|
||||
CellInfo *iol;
|
||||
if (pio_iologic.count(pio->name))
|
||||
iol = pio_iologic.at(pio->name);
|
||||
else
|
||||
iol = create_pio_iologic(pio, ci);
|
||||
set_iologic_mode(iol, "IDDRX1_ODDRX1");
|
||||
replace_port(ci, ctx->id("D"), iol, id_PADDI);
|
||||
set_iologic_sclk(iol, ci, ctx->id("SCLK"), true);
|
||||
set_iologic_lsr(iol, ci, ctx->id("RST"), true);
|
||||
replace_port(ci, ctx->id("Q0"), iol, id_RXDATA0);
|
||||
replace_port(ci, ctx->id("Q1"), iol, id_RXDATA1);
|
||||
iol->params[ctx->id("GSR")] = str_or_default(ci->params, ctx->id("GSR"), "DISABLED");
|
||||
packed_cells.insert(cell.first);
|
||||
} else if (ci->type == ctx->id("ODDRX1F")) {
|
||||
CellInfo *pio = net_only_drives(ctx, ci->ports.at(ctx->id("Q")).net, is_trellis_io, id_I, true);
|
||||
if (pio == nullptr)
|
||||
log_error("ODDRX1F '%s' Q output must be connected only to a top level output\n",
|
||||
ci->name.c_str(ctx));
|
||||
CellInfo *iol;
|
||||
if (pio_iologic.count(pio->name))
|
||||
iol = pio_iologic.at(pio->name);
|
||||
else
|
||||
iol = create_pio_iologic(pio, ci);
|
||||
set_iologic_mode(iol, "IDDRX1_ODDRX1");
|
||||
replace_port(ci, ctx->id("Q"), iol, id_IOLDO);
|
||||
replace_port(pio, id_I, pio, id_IOLDO);
|
||||
pio->params[ctx->id("DATAMUX_ODDR")] = "IOLDO";
|
||||
set_iologic_sclk(iol, ci, ctx->id("SCLK"), false);
|
||||
set_iologic_lsr(iol, ci, ctx->id("RST"), false);
|
||||
replace_port(ci, ctx->id("D0"), iol, id_TXDATA0);
|
||||
replace_port(ci, ctx->id("D1"), iol, id_TXDATA1);
|
||||
iol->params[ctx->id("GSR")] = str_or_default(ci->params, ctx->id("GSR"), "DISABLED");
|
||||
packed_cells.insert(cell.first);
|
||||
}
|
||||
}
|
||||
flush_cells();
|
||||
};
|
||||
|
||||
public:
|
||||
void pack()
|
||||
{
|
||||
pack_io();
|
||||
pack_iologic();
|
||||
pack_ebr();
|
||||
pack_dsps();
|
||||
pack_dcus();
|
||||
|
Loading…
Reference in New Issue
Block a user