Merge pull request #83 from YosysHQ/ecp5_dram
Adding support for ECP5 distributed RAM
This commit is contained in:
commit
2297352772
@ -19,6 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "design_utils.h"
|
#include "design_utils.h"
|
||||||
|
#include <algorithm>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
@ -76,6 +77,8 @@ void print_utilisation(const Context *ctx)
|
|||||||
// Connect a net to a port
|
// Connect a net to a port
|
||||||
void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString port_name)
|
void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString port_name)
|
||||||
{
|
{
|
||||||
|
if (net == nullptr)
|
||||||
|
return;
|
||||||
PortInfo &port = cell->ports.at(port_name);
|
PortInfo &port = cell->ports.at(port_name);
|
||||||
NPNR_ASSERT(port.net == nullptr);
|
NPNR_ASSERT(port.net == nullptr);
|
||||||
port.net = net;
|
port.net = net;
|
||||||
@ -93,4 +96,33 @@ void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString por
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void disconnect_port(const Context *ctx, CellInfo *cell, IdString port_name)
|
||||||
|
{
|
||||||
|
if (!cell->ports.count(port_name))
|
||||||
|
return;
|
||||||
|
PortInfo &port = cell->ports.at(port_name);
|
||||||
|
if (port.net != nullptr) {
|
||||||
|
port.net->users.erase(std::remove_if(port.net->users.begin(), port.net->users.end(),
|
||||||
|
[cell, port_name](const PortRef &user) {
|
||||||
|
return user.cell == cell && user.port == port_name;
|
||||||
|
}),
|
||||||
|
port.net->users.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void connect_ports(Context *ctx, CellInfo *cell1, IdString port1_name, CellInfo *cell2, IdString port2_name)
|
||||||
|
{
|
||||||
|
PortInfo &port1 = cell1->ports.at(port1_name);
|
||||||
|
if (port1.net == nullptr) {
|
||||||
|
// No net on port1; need to create one
|
||||||
|
std::unique_ptr<NetInfo> p1net(new NetInfo());
|
||||||
|
p1net->name = ctx->id(cell1->name.str(ctx) + "$conn$" + port1_name.str(ctx));
|
||||||
|
connect_port(ctx, p1net.get(), cell1, port1_name);
|
||||||
|
IdString p1name = p1net->name;
|
||||||
|
NPNR_ASSERT(!ctx->cells.count(p1name));
|
||||||
|
ctx->nets[p1name] = std::move(p1net);
|
||||||
|
}
|
||||||
|
connect_port(ctx, port1.net, cell2, port2_name);
|
||||||
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -85,6 +85,12 @@ template <typename F1> CellInfo *net_driven_by(const Context *ctx, const NetInfo
|
|||||||
// Connect a net to a port
|
// Connect a net to a port
|
||||||
void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString port_name);
|
void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString port_name);
|
||||||
|
|
||||||
|
// Disconnect a net from a port
|
||||||
|
void disconnect_port(const Context *ctx, CellInfo *cell, IdString port_name);
|
||||||
|
|
||||||
|
// Connect two ports together
|
||||||
|
void connect_ports(Context *ctx, CellInfo *cell1, IdString port1_name, CellInfo *cell2, IdString port2_name);
|
||||||
|
|
||||||
void print_utilisation(const Context *ctx);
|
void print_utilisation(const Context *ctx);
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -245,6 +245,11 @@ void Context::check() const
|
|||||||
NPNR_ASSERT(ni == getBoundPipNet(w.second.pip));
|
NPNR_ASSERT(ni == getBoundPipNet(w.second.pip));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ni->driver.cell != nullptr)
|
||||||
|
NPNR_ASSERT(ni->driver.cell->ports.at(ni->driver.port).net == ni);
|
||||||
|
for (auto user : ni->users) {
|
||||||
|
NPNR_ASSERT(user.cell->ports.at(user.port).net == ni);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto w : getWires()) {
|
for (auto w : getWires()) {
|
||||||
|
@ -164,6 +164,22 @@ struct Timing
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check to ensure that all ports where fanins were recorded were indeed visited
|
// Sanity check to ensure that all ports where fanins were recorded were indeed visited
|
||||||
|
if (!port_fanin.empty()) {
|
||||||
|
for (auto fanin : port_fanin) {
|
||||||
|
NetInfo *net = fanin.first->net;
|
||||||
|
if (net != nullptr) {
|
||||||
|
log_info(" remaining fanin includes %s (net %s)\n", fanin.first->name.c_str(ctx),
|
||||||
|
net->name.c_str(ctx));
|
||||||
|
if (net->driver.cell != nullptr)
|
||||||
|
log_info(" driver = %s.%s\n", net->driver.cell->name.c_str(ctx),
|
||||||
|
net->driver.port.c_str(ctx));
|
||||||
|
for (auto net_user : net->users)
|
||||||
|
log_info(" user: %s.%s\n", net_user.cell->name.c_str(ctx), net_user.port.c_str(ctx));
|
||||||
|
} else {
|
||||||
|
log_info(" remaining fanin includes %s (no net)\n", fanin.first->name.c_str(ctx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
NPNR_ASSERT(port_fanin.empty());
|
NPNR_ASSERT(port_fanin.empty());
|
||||||
|
|
||||||
// Go forwards topographically to find the maximum arrival time and max path length for each net
|
// Go forwards topographically to find the maximum arrival time and max path length for each net
|
||||||
|
@ -511,12 +511,12 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
|
|||||||
delay.delay = 193;
|
delay.delay = 193;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#if 0 // FIXME
|
||||||
if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) {
|
if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) {
|
||||||
delay.delay = 717;
|
delay.delay = 717;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) ||
|
if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) ||
|
||||||
(fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) ||
|
(fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) ||
|
||||||
(fromPort == id_C0 && toPort == id_WADO2) || (fromPort == id_C1 && toPort == id_WDO0) ||
|
(fromPort == id_C0 && toPort == id_WADO2) || (fromPort == id_C1 && toPort == id_WDO0) ||
|
||||||
|
@ -260,6 +260,17 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
|
|||||||
str_or_default(ci->params, ctx->id("SRMODE"), "LSR_OVER_CE"));
|
str_or_default(ci->params, ctx->id("SRMODE"), "LSR_OVER_CE"));
|
||||||
cc.tiles[tname].add_enum("LSR1.LSRMUX", str_or_default(ci->params, ctx->id("LSRMUX"), "LSR"));
|
cc.tiles[tname].add_enum("LSR1.LSRMUX", str_or_default(ci->params, ctx->id("LSRMUX"), "LSR"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetInfo *clknet = nullptr;
|
||||||
|
if (ci->ports.find(ctx->id("CLK")) != ci->ports.end() && ci->ports.at(ctx->id("CLK")).net != nullptr)
|
||||||
|
clknet = ci->ports.at(ctx->id("CLK")).net;
|
||||||
|
if (ctx->getBoundWireNet(ctx->getWireByName(
|
||||||
|
ctx->id(fmt_str("X" << bel.location.x << "/Y" << bel.location.y << "/CLK0")))) == clknet) {
|
||||||
|
cc.tiles[tname].add_enum("CLK0.CLKMUX", str_or_default(ci->params, ctx->id("CLKMUX"), "CLK"));
|
||||||
|
} else if (ctx->getBoundWireNet(ctx->getWireByName(ctx->id(
|
||||||
|
fmt_str("X" << bel.location.x << "/Y" << bel.location.y << "/CLK1")))) == clknet) {
|
||||||
|
cc.tiles[tname].add_enum("CLK1.CLKMUX", str_or_default(ci->params, ctx->id("CLKMUX"), "CLK"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str_or_default(ci->params, ctx->id("MODE"), "LOGIC") == "CCU2") {
|
if (str_or_default(ci->params, ctx->id("MODE"), "LOGIC") == "CCU2") {
|
||||||
@ -267,6 +278,23 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
|
|||||||
str_or_default(ci->params, ctx->id("INJECT1_0"), "YES"));
|
str_or_default(ci->params, ctx->id("INJECT1_0"), "YES"));
|
||||||
cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_1",
|
cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_1",
|
||||||
str_or_default(ci->params, ctx->id("INJECT1_1"), "YES"));
|
str_or_default(ci->params, ctx->id("INJECT1_1"), "YES"));
|
||||||
|
} else {
|
||||||
|
// Don't interfere with cascade mux wiring
|
||||||
|
cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_0",
|
||||||
|
str_or_default(ci->params, ctx->id("INJECT1_0"), "_NONE_"));
|
||||||
|
cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_1",
|
||||||
|
str_or_default(ci->params, ctx->id("INJECT1_1"), "_NONE_"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_or_default(ci->params, ctx->id("MODE"), "LOGIC") == "DPRAM" && slice == "SLICEA") {
|
||||||
|
cc.tiles[tname].add_enum(slice + ".WREMUX", str_or_default(ci->params, ctx->id("WREMUX"), "WRE"));
|
||||||
|
|
||||||
|
NetInfo *wcknet = nullptr;
|
||||||
|
std::string wckmux = str_or_default(ci->params, ctx->id("WCKMUX"), "WCK");
|
||||||
|
wckmux = (wckmux == "WCK") ? "CLK" : wckmux;
|
||||||
|
if (ci->ports.find(ctx->id("WCK")) != ci->ports.end() && ci->ports.at(ctx->id("WCK")).net != nullptr)
|
||||||
|
wcknet = ci->ports.at(ctx->id("WCK")).net;
|
||||||
|
cc.tiles[tname].add_enum("CLK1.CLKMUX", wckmux);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tie unused inputs high
|
// Tie unused inputs high
|
||||||
|
105
ecp5/cells.cc
105
ecp5/cells.cc
@ -185,6 +185,8 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive
|
|||||||
set_param_safe(has_ff, lc, ctx->id("GSR"), str_or_default(ff->params, ctx->id("GSR"), "DISABLED"));
|
set_param_safe(has_ff, lc, ctx->id("GSR"), str_or_default(ff->params, ctx->id("GSR"), "DISABLED"));
|
||||||
set_param_safe(has_ff, lc, ctx->id("CEMUX"), str_or_default(ff->params, ctx->id("CEMUX"), "1"));
|
set_param_safe(has_ff, lc, ctx->id("CEMUX"), str_or_default(ff->params, ctx->id("CEMUX"), "1"));
|
||||||
set_param_safe(has_ff, lc, ctx->id("LSRMUX"), str_or_default(ff->params, ctx->id("LSRMUX"), "LSR"));
|
set_param_safe(has_ff, lc, ctx->id("LSRMUX"), str_or_default(ff->params, ctx->id("LSRMUX"), "LSR"));
|
||||||
|
set_param_safe(has_ff, lc, ctx->id("CLKMUX"), str_or_default(ff->params, ctx->id("CLKMUX"), "CLK"));
|
||||||
|
|
||||||
lc->params[ctx->id(reg + "_SD")] = driven_by_lut ? "1" : "0";
|
lc->params[ctx->id(reg + "_SD")] = driven_by_lut ? "1" : "0";
|
||||||
lc->params[ctx->id(reg + "_REGSET")] = str_or_default(ff->params, ctx->id("REGSET"), "RESET");
|
lc->params[ctx->id(reg + "_REGSET")] = str_or_default(ff->params, ctx->id("REGSET"), "RESET");
|
||||||
replace_port_safe(has_ff, ff, ctx->id("CLK"), lc, ctx->id("CLK"));
|
replace_port_safe(has_ff, ff, ctx->id("CLK"), lc, ctx->id("CLK"));
|
||||||
@ -238,4 +240,107 @@ void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc)
|
|||||||
replace_port(ccu, ctx->id("COUT"), lc, ctx->id("FCO"));
|
replace_port(ccu, ctx->id("COUT"), lc, ctx->id("FCO"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dram_to_ramw(Context *ctx, CellInfo *ram, CellInfo *lc)
|
||||||
|
{
|
||||||
|
lc->params[ctx->id("MODE")] = "RAMW";
|
||||||
|
replace_port(ram, ctx->id("WAD[0]"), lc, ctx->id("D0"));
|
||||||
|
replace_port(ram, ctx->id("WAD[1]"), lc, ctx->id("B0"));
|
||||||
|
replace_port(ram, ctx->id("WAD[2]"), lc, ctx->id("C0"));
|
||||||
|
replace_port(ram, ctx->id("WAD[3]"), lc, ctx->id("A0"));
|
||||||
|
|
||||||
|
replace_port(ram, ctx->id("DI[0]"), lc, ctx->id("C1"));
|
||||||
|
replace_port(ram, ctx->id("DI[1]"), lc, ctx->id("A1"));
|
||||||
|
replace_port(ram, ctx->id("DI[2]"), lc, ctx->id("D1"));
|
||||||
|
replace_port(ram, ctx->id("DI[3]"), lc, ctx->id("B1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned get_dram_init(const Context *ctx, const CellInfo *ram, int bit)
|
||||||
|
{
|
||||||
|
const std::string &idata = str_or_default(ram->params, ctx->id("INITVAL"),
|
||||||
|
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
||||||
|
NPNR_ASSERT(idata.length() == 64);
|
||||||
|
unsigned value = 0;
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
char c = idata.at(63 - (4 * i + bit));
|
||||||
|
if (c == '1')
|
||||||
|
value |= (1 << i);
|
||||||
|
else
|
||||||
|
NPNR_ASSERT(c == '0' || c == 'x');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw, int index)
|
||||||
|
{
|
||||||
|
lc->params[ctx->id("MODE")] = "DPRAM";
|
||||||
|
lc->params[ctx->id("WREMUX")] = str_or_default(ram->params, ctx->id("WREMUX"), "WRE");
|
||||||
|
lc->params[ctx->id("WCKMUX")] = str_or_default(ram->params, ctx->id("WCKMUX"), "WCK");
|
||||||
|
|
||||||
|
unsigned permuted_init0 = 0, permuted_init1 = 0;
|
||||||
|
unsigned init0 = get_dram_init(ctx, ram, index * 2), init1 = get_dram_init(ctx, ram, index * 2 + 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
int permuted_addr = 0;
|
||||||
|
if (i & 1)
|
||||||
|
permuted_addr |= 8;
|
||||||
|
if (i & 2)
|
||||||
|
permuted_addr |= 2;
|
||||||
|
if (i & 4)
|
||||||
|
permuted_addr |= 4;
|
||||||
|
if (i & 8)
|
||||||
|
permuted_addr |= 1;
|
||||||
|
if (init0 & (1 << permuted_addr))
|
||||||
|
permuted_init0 |= (1 << i);
|
||||||
|
if (init1 & (1 << permuted_addr))
|
||||||
|
permuted_init1 |= (1 << i);
|
||||||
|
}
|
||||||
|
|
||||||
|
lc->params[ctx->id("LUT0_INITVAL")] = std::to_string(permuted_init0);
|
||||||
|
lc->params[ctx->id("LUT1_INITVAL")] = std::to_string(permuted_init1);
|
||||||
|
|
||||||
|
if (ram->ports.count(ctx->id("RAD[0]"))) {
|
||||||
|
connect_port(ctx, ram->ports.at(ctx->id("RAD[0]")).net, lc, ctx->id("D0"));
|
||||||
|
connect_port(ctx, ram->ports.at(ctx->id("RAD[0]")).net, lc, ctx->id("D1"));
|
||||||
|
}
|
||||||
|
if (ram->ports.count(ctx->id("RAD[1]"))) {
|
||||||
|
connect_port(ctx, ram->ports.at(ctx->id("RAD[1]")).net, lc, ctx->id("B0"));
|
||||||
|
connect_port(ctx, ram->ports.at(ctx->id("RAD[1]")).net, lc, ctx->id("B1"));
|
||||||
|
}
|
||||||
|
if (ram->ports.count(ctx->id("RAD[2]"))) {
|
||||||
|
connect_port(ctx, ram->ports.at(ctx->id("RAD[2]")).net, lc, ctx->id("C0"));
|
||||||
|
connect_port(ctx, ram->ports.at(ctx->id("RAD[2]")).net, lc, ctx->id("C1"));
|
||||||
|
}
|
||||||
|
if (ram->ports.count(ctx->id("RAD[3]"))) {
|
||||||
|
connect_port(ctx, ram->ports.at(ctx->id("RAD[3]")).net, lc, ctx->id("A0"));
|
||||||
|
connect_port(ctx, ram->ports.at(ctx->id("RAD[3]")).net, lc, ctx->id("A1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ram->ports.count(ctx->id("WRE")))
|
||||||
|
connect_port(ctx, ram->ports.at(ctx->id("WRE")).net, lc, ctx->id("WRE"));
|
||||||
|
if (ram->ports.count(ctx->id("WCK")))
|
||||||
|
connect_port(ctx, ram->ports.at(ctx->id("WCK")).net, lc, ctx->id("WCK"));
|
||||||
|
|
||||||
|
connect_ports(ctx, ramw, id_WADO0, lc, id_WAD0);
|
||||||
|
connect_ports(ctx, ramw, id_WADO1, lc, id_WAD1);
|
||||||
|
connect_ports(ctx, ramw, id_WADO2, lc, id_WAD2);
|
||||||
|
connect_ports(ctx, ramw, id_WADO3, lc, id_WAD3);
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
connect_ports(ctx, ramw, id_WDO0, lc, id_WD0);
|
||||||
|
connect_ports(ctx, ramw, id_WDO1, lc, id_WD1);
|
||||||
|
|
||||||
|
replace_port(ram, ctx->id("DO[0]"), lc, id_F0);
|
||||||
|
replace_port(ram, ctx->id("DO[1]"), lc, id_F1);
|
||||||
|
|
||||||
|
} else if (index == 1) {
|
||||||
|
connect_ports(ctx, ramw, id_WDO2, lc, id_WD0);
|
||||||
|
connect_ports(ctx, ramw, id_WDO3, lc, id_WD1);
|
||||||
|
|
||||||
|
replace_port(ram, ctx->id("DO[2]"), lc, id_F0);
|
||||||
|
replace_port(ram, ctx->id("DO[3]"), lc, id_F1);
|
||||||
|
} else {
|
||||||
|
NPNR_ASSERT_FALSE("bad DPRAM index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -49,6 +49,8 @@ inline bool is_l6mux(const BaseCtx *ctx, const CellInfo *cell) { return cell->ty
|
|||||||
void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut);
|
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 lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index);
|
||||||
void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc);
|
void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc);
|
||||||
|
void dram_to_ramw(Context *ctx, CellInfo *ram, CellInfo *lc);
|
||||||
|
void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw, int index);
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class Ecp5GlobalRouter
|
|||||||
private:
|
private:
|
||||||
bool is_clock_port(const PortRef &user)
|
bool is_clock_port(const PortRef &user)
|
||||||
{
|
{
|
||||||
if (user.cell->type == id_TRELLIS_SLICE && user.port == id_CLK)
|
if (user.cell->type == id_TRELLIS_SLICE && (user.port == id_CLK || user.port == id_WCK))
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -302,6 +302,14 @@ class Ecp5GlobalRouter
|
|||||||
ctx->nets[glbnet->name] = std::move(glbnet);
|
ctx->nets[glbnet->name] = std::move(glbnet);
|
||||||
return glbptr;
|
return glbptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int global_route_priority(const PortRef &load)
|
||||||
|
{
|
||||||
|
if (load.port == id_WCK || load.port == id_WRE)
|
||||||
|
return 90;
|
||||||
|
return 99;
|
||||||
|
}
|
||||||
|
|
||||||
Context *ctx;
|
Context *ctx;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -333,7 +341,13 @@ class Ecp5GlobalRouter
|
|||||||
NetInfo *global = insert_dcc(clock);
|
NetInfo *global = insert_dcc(clock);
|
||||||
bool routed = route_onto_global(global, glbid);
|
bool routed = route_onto_global(global, glbid);
|
||||||
NPNR_ASSERT(routed);
|
NPNR_ASSERT(routed);
|
||||||
for (const auto &user : global->users) {
|
|
||||||
|
// WCK must have routing priority
|
||||||
|
auto sorted_users = global->users;
|
||||||
|
std::sort(sorted_users.begin(), sorted_users.end(), [this](const PortRef &a, const PortRef &b) {
|
||||||
|
return global_route_priority(a) < global_route_priority(b);
|
||||||
|
});
|
||||||
|
for (const auto &user : sorted_users) {
|
||||||
route_logic_tile_global(global, glbid, user);
|
route_logic_tile_global(global, glbid, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
122
ecp5/pack.cc
122
ecp5/pack.cc
@ -108,7 +108,7 @@ class Ecp5Packer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return whether or not an FF can be added to a tile (pairing checks must also be done using the fn above)
|
// Return whether or not an FF can be added to a tile (pairing checks must also be done using the fn above)
|
||||||
bool can_add_ff_to_file(const std::vector<CellInfo *> &tile_ffs, CellInfo *ff0)
|
bool can_add_ff_to_tile(const std::vector<CellInfo *> &tile_ffs, CellInfo *ff0)
|
||||||
{
|
{
|
||||||
for (const auto &existing : tile_ffs) {
|
for (const auto &existing : tile_ffs) {
|
||||||
if (net_or_nullptr(existing, ctx->id("CLK")) != net_or_nullptr(ff0, ctx->id("CLK")))
|
if (net_or_nullptr(existing, ctx->id("CLK")) != net_or_nullptr(ff0, ctx->id("CLK")))
|
||||||
@ -128,6 +128,20 @@ class Ecp5Packer
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return true if a FF can be added to a DPRAM slice
|
||||||
|
bool can_pack_ff_dram(CellInfo *dpram, CellInfo *ff)
|
||||||
|
{
|
||||||
|
std::string wckmux = str_or_default(dpram->params, ctx->id("WCKMUX"), "WCK");
|
||||||
|
std::string clkmux = str_or_default(ff->params, ctx->id("CLKMUX"), "CLK");
|
||||||
|
if (wckmux != clkmux && !(wckmux == "WCK" && clkmux == "CLK"))
|
||||||
|
return false;
|
||||||
|
std::string wremux = str_or_default(dpram->params, ctx->id("WREMUX"), "WRE");
|
||||||
|
std::string lsrmux = str_or_default(ff->params, ctx->id("LSRMUX"), "LSR");
|
||||||
|
if (wremux != lsrmux && !(wremux == "WRE" && lsrmux == "LSR"))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Return true if two LUTs can be paired considering FF compatibility
|
// Return true if two LUTs can be paired considering FF compatibility
|
||||||
bool can_pack_lutff(IdString lut0, IdString lut1)
|
bool can_pack_lutff(IdString lut0, IdString lut1)
|
||||||
{
|
{
|
||||||
@ -496,6 +510,7 @@ class Ecp5Packer
|
|||||||
std::vector<std::vector<CellInfo *>> packed_chains;
|
std::vector<std::vector<CellInfo *>> packed_chains;
|
||||||
|
|
||||||
// Chain packing
|
// Chain packing
|
||||||
|
std::vector<std::tuple<CellInfo *, CellInfo *, int>> ff_packing;
|
||||||
for (auto &chain : all_chains) {
|
for (auto &chain : all_chains) {
|
||||||
int cell_count = 0;
|
int cell_count = 0;
|
||||||
std::vector<CellInfo *> tile_ffs;
|
std::vector<CellInfo *> tile_ffs;
|
||||||
@ -512,8 +527,8 @@ class Ecp5Packer
|
|||||||
NetInfo *f0net = slice->ports.at(ctx->id("F0")).net;
|
NetInfo *f0net = slice->ports.at(ctx->id("F0")).net;
|
||||||
if (f0net != nullptr) {
|
if (f0net != nullptr) {
|
||||||
ff0 = net_only_drives(ctx, f0net, is_ff, ctx->id("DI"), false);
|
ff0 = net_only_drives(ctx, f0net, is_ff, ctx->id("DI"), false);
|
||||||
if (ff0 != nullptr && can_add_ff_to_file(tile_ffs, ff0)) {
|
if (ff0 != nullptr && can_add_ff_to_tile(tile_ffs, ff0)) {
|
||||||
ff_to_slice(ctx, ff0, slice.get(), 0, true);
|
ff_packing.push_back(std::make_tuple(ff0, slice.get(), 0));
|
||||||
tile_ffs.push_back(ff0);
|
tile_ffs.push_back(ff0);
|
||||||
packed_cells.insert(ff0->name);
|
packed_cells.insert(ff0->name);
|
||||||
}
|
}
|
||||||
@ -524,8 +539,8 @@ class Ecp5Packer
|
|||||||
if (f1net != nullptr) {
|
if (f1net != nullptr) {
|
||||||
ff1 = net_only_drives(ctx, f1net, is_ff, ctx->id("DI"), false);
|
ff1 = net_only_drives(ctx, f1net, is_ff, ctx->id("DI"), false);
|
||||||
if (ff1 != nullptr && (ff0 == nullptr || can_pack_ffs(ff0, ff1)) &&
|
if (ff1 != nullptr && (ff0 == nullptr || can_pack_ffs(ff0, ff1)) &&
|
||||||
can_add_ff_to_file(tile_ffs, ff1)) {
|
can_add_ff_to_tile(tile_ffs, ff1)) {
|
||||||
ff_to_slice(ctx, ff1, slice.get(), 1, true);
|
ff_packing.push_back(std::make_tuple(ff1, slice.get(), 1));
|
||||||
tile_ffs.push_back(ff1);
|
tile_ffs.push_back(ff1);
|
||||||
packed_cells.insert(ff1->name);
|
packed_cells.insert(ff1->name);
|
||||||
}
|
}
|
||||||
@ -538,6 +553,9 @@ class Ecp5Packer
|
|||||||
packed_chains.push_back(packed_chain);
|
packed_chains.push_back(packed_chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto ff : ff_packing)
|
||||||
|
ff_to_slice(ctx, std::get<0>(ff), std::get<1>(ff), std::get<2>(ff), true);
|
||||||
|
|
||||||
// Relative chain placement
|
// Relative chain placement
|
||||||
for (auto &chain : packed_chains) {
|
for (auto &chain : packed_chains) {
|
||||||
chain.at(0)->constr_abs_z = true;
|
chain.at(0)->constr_abs_z = true;
|
||||||
@ -555,6 +573,98 @@ class Ecp5Packer
|
|||||||
flush_cells();
|
flush_cells();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pack distributed RAM
|
||||||
|
void pack_dram()
|
||||||
|
{
|
||||||
|
for (auto cell : sorted(ctx->cells)) {
|
||||||
|
CellInfo *ci = cell.second;
|
||||||
|
if (is_dpram(ctx, ci)) {
|
||||||
|
|
||||||
|
// Create RAMW slice
|
||||||
|
std::unique_ptr<CellInfo> ramw_slice =
|
||||||
|
create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "$RAMW_SLICE");
|
||||||
|
dram_to_ramw(ctx, ci, ramw_slice.get());
|
||||||
|
|
||||||
|
// Create actual RAM slices
|
||||||
|
std::unique_ptr<CellInfo> ram0_slice =
|
||||||
|
create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "$DPRAM0_SLICE");
|
||||||
|
dram_to_ram_slice(ctx, ci, ram0_slice.get(), ramw_slice.get(), 0);
|
||||||
|
|
||||||
|
std::unique_ptr<CellInfo> ram1_slice =
|
||||||
|
create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "$DPRAM1_SLICE");
|
||||||
|
dram_to_ram_slice(ctx, ci, ram1_slice.get(), ramw_slice.get(), 1);
|
||||||
|
|
||||||
|
// Disconnect ports of original cell after packing
|
||||||
|
disconnect_port(ctx, ci, id_WCK);
|
||||||
|
disconnect_port(ctx, ci, id_WRE);
|
||||||
|
|
||||||
|
disconnect_port(ctx, ci, ctx->id("RAD[0]"));
|
||||||
|
disconnect_port(ctx, ci, ctx->id("RAD[1]"));
|
||||||
|
disconnect_port(ctx, ci, ctx->id("RAD[2]"));
|
||||||
|
disconnect_port(ctx, ci, ctx->id("RAD[3]"));
|
||||||
|
|
||||||
|
// Attempt to pack FFs into RAM slices
|
||||||
|
std::vector<std::tuple<CellInfo *, CellInfo *, int>> ff_packing;
|
||||||
|
std::vector<CellInfo *> tile_ffs;
|
||||||
|
for (auto slice : {ram0_slice.get(), ram1_slice.get()}) {
|
||||||
|
CellInfo *ff0 = nullptr;
|
||||||
|
NetInfo *f0net = slice->ports.at(ctx->id("F0")).net;
|
||||||
|
if (f0net != nullptr) {
|
||||||
|
ff0 = net_only_drives(ctx, f0net, is_ff, ctx->id("DI"), false);
|
||||||
|
if (ff0 != nullptr && can_add_ff_to_tile(tile_ffs, ff0)) {
|
||||||
|
if (can_pack_ff_dram(slice, ff0)) {
|
||||||
|
ff_packing.push_back(std::make_tuple(ff0, slice, 0));
|
||||||
|
tile_ffs.push_back(ff0);
|
||||||
|
packed_cells.insert(ff0->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CellInfo *ff1 = nullptr;
|
||||||
|
NetInfo *f1net = slice->ports.at(ctx->id("F1")).net;
|
||||||
|
if (f1net != nullptr) {
|
||||||
|
ff1 = net_only_drives(ctx, f1net, is_ff, ctx->id("DI"), false);
|
||||||
|
if (ff1 != nullptr && (ff0 == nullptr || can_pack_ffs(ff0, ff1)) &&
|
||||||
|
can_add_ff_to_tile(tile_ffs, ff1)) {
|
||||||
|
if (can_pack_ff_dram(slice, ff1)) {
|
||||||
|
ff_packing.push_back(std::make_tuple(ff1, slice, 1));
|
||||||
|
tile_ffs.push_back(ff1);
|
||||||
|
packed_cells.insert(ff1->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto ff : ff_packing)
|
||||||
|
ff_to_slice(ctx, std::get<0>(ff), std::get<1>(ff), std::get<2>(ff), true);
|
||||||
|
|
||||||
|
// Setup placement constraints
|
||||||
|
ram0_slice->constr_abs_z = true;
|
||||||
|
ram0_slice->constr_z = 0;
|
||||||
|
|
||||||
|
ram1_slice->constr_parent = ram0_slice.get();
|
||||||
|
ram1_slice->constr_abs_z = true;
|
||||||
|
ram1_slice->constr_x = 0;
|
||||||
|
ram1_slice->constr_y = 0;
|
||||||
|
ram1_slice->constr_z = 1;
|
||||||
|
ram0_slice->constr_children.push_back(ram1_slice.get());
|
||||||
|
|
||||||
|
ramw_slice->constr_parent = ram0_slice.get();
|
||||||
|
ramw_slice->constr_abs_z = true;
|
||||||
|
ramw_slice->constr_x = 0;
|
||||||
|
ramw_slice->constr_y = 0;
|
||||||
|
ramw_slice->constr_z = 2;
|
||||||
|
ram0_slice->constr_children.push_back(ramw_slice.get());
|
||||||
|
|
||||||
|
new_cells.push_back(std::move(ram0_slice));
|
||||||
|
new_cells.push_back(std::move(ram1_slice));
|
||||||
|
new_cells.push_back(std::move(ramw_slice));
|
||||||
|
packed_cells.insert(ci->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush_cells();
|
||||||
|
}
|
||||||
|
|
||||||
// Pack LUTs that have been paired together
|
// Pack LUTs that have been paired together
|
||||||
void pack_lut_pairs()
|
void pack_lut_pairs()
|
||||||
{
|
{
|
||||||
@ -804,6 +914,7 @@ class Ecp5Packer
|
|||||||
{
|
{
|
||||||
pack_io();
|
pack_io();
|
||||||
pack_constants();
|
pack_constants();
|
||||||
|
pack_dram();
|
||||||
pack_carries();
|
pack_carries();
|
||||||
find_lutff_pairs();
|
find_lutff_pairs();
|
||||||
pack_lut5s();
|
pack_lut5s();
|
||||||
@ -811,6 +922,7 @@ class Ecp5Packer
|
|||||||
pack_lut_pairs();
|
pack_lut_pairs();
|
||||||
pack_remaining_luts();
|
pack_remaining_luts();
|
||||||
pack_remaining_ffs();
|
pack_remaining_ffs();
|
||||||
|
ctx->check();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Loading…
Reference in New Issue
Block a user