mirror of https://github.com/YosysHQ/nextpnr.git
Gowin. Allow clock network routing from GP pins. (#1518)
Adds automatic connection of a general-purpose pin to the global clock network. The old behaviour, where such networks have to be explicitly specified, can be activated with the command line key "--vopt disable_gp_clock_routing". Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
This commit is contained in:
parent
840354a28a
commit
4831e50843
|
|
@ -126,6 +126,7 @@ X(MCLK)
|
|||
X(CLK0)
|
||||
X(CLK1)
|
||||
X(CLK2)
|
||||
X(CLK3)
|
||||
X(LSR0)
|
||||
X(LSR1)
|
||||
X(LSR2)
|
||||
|
|
@ -1358,3 +1359,8 @@ X(LW_TAP)
|
|||
X(LW_TAP_0)
|
||||
X(LW_BRANCH)
|
||||
X(SEG_WIRES_TO_ISOLATE)
|
||||
|
||||
// routing params
|
||||
X(NO_GP_CLOCK_ROUTING)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -248,69 +248,6 @@ struct GowinGlobalRouter
|
|||
}
|
||||
}
|
||||
|
||||
bool driver_is_buf(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_BUFG, id_O); }
|
||||
bool driver_is_dqce(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DQCE, id_CLKOUT); }
|
||||
bool driver_is_dcs(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DCS, id_CLKOUT); }
|
||||
bool driver_is_dhcen(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DHCEN, id_CLKOUT); }
|
||||
bool driver_is_mipi(const PortRef &driver)
|
||||
{
|
||||
return CellTypePort(driver) == CellTypePort(id_IOBUF, id_O) && driver.cell->params.count(id_MIPI_IBUF);
|
||||
}
|
||||
bool driver_is_clksrc(const PortRef &driver)
|
||||
{
|
||||
// dedicated pins
|
||||
if (CellTypePort(driver) == CellTypePort(id_IBUF, id_O)) {
|
||||
|
||||
NPNR_ASSERT(driver.cell->bel != BelId());
|
||||
IdStringList pin_func = gwu.get_pin_funcs(driver.cell->bel);
|
||||
for (size_t i = 0; i < pin_func.size(); ++i) {
|
||||
if (ctx->debug) {
|
||||
log_info("bel:%s, pin func: %zu:%s\n", ctx->nameOfBel(driver.cell->bel), i,
|
||||
pin_func[i].str(ctx).c_str());
|
||||
}
|
||||
if (pin_func[i].str(ctx).rfind("GCLKT", 0) == 0) {
|
||||
if (ctx->debug) {
|
||||
log_info("Clock pin:%s:%s\n", ctx->getBelName(driver.cell->bel).str(ctx).c_str(),
|
||||
pin_func[i].c_str(ctx));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// PLL outputs
|
||||
if (driver.cell->type.in(id_rPLL, id_PLLVR)) {
|
||||
if (driver.port.in(id_CLKOUT, id_CLKOUTD, id_CLKOUTD3, id_CLKOUTP)) {
|
||||
if (ctx->debug) {
|
||||
log_info("PLL out:%s:%s\n", ctx->getBelName(driver.cell->bel).str(ctx).c_str(),
|
||||
driver.port.c_str(ctx));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// HCLK outputs
|
||||
if (driver.cell->type.in(id_CLKDIV, id_CLKDIV2)) {
|
||||
if (driver.port.in(id_CLKOUT)) {
|
||||
if (ctx->debug) {
|
||||
log_info("%s out:%s:%s:%s\n", driver.cell->type.c_str(ctx),
|
||||
ctx->getBelName(driver.cell->bel).str(ctx).c_str(), driver.port.c_str(ctx),
|
||||
ctx->nameOfWire(ctx->getBelPinWire(driver.cell->bel, driver.port)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// DLLDLY outputs
|
||||
if (driver.cell->type == id_DLLDLY) {
|
||||
if (driver.port.in(id_CLKOUT)) {
|
||||
if (ctx->debug) {
|
||||
log_info("%s out:%s:%s\n", driver.cell->type.c_str(ctx),
|
||||
ctx->getBelName(driver.cell->bel).str(ctx).c_str(), driver.port.c_str(ctx));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
enum RouteResult
|
||||
{
|
||||
NOT_ROUTED = 0,
|
||||
|
|
@ -367,11 +304,11 @@ struct GowinGlobalRouter
|
|||
NPNR_ASSERT(net_before_dqce != nullptr);
|
||||
|
||||
PortRef driver = net_before_dqce->driver;
|
||||
NPNR_ASSERT_MSG(driver_is_buf(driver) || driver_is_clksrc(driver),
|
||||
NPNR_ASSERT_MSG(gwu.driver_is_buf(driver) || gwu.driver_is_clksrc(driver),
|
||||
stringf("The input source for %s is not a clock.", ctx->nameOf(dqce_ci)).c_str());
|
||||
WireId src;
|
||||
// use BUF input if there is one
|
||||
if (driver_is_buf(driver)) {
|
||||
if (gwu.driver_is_buf(driver)) {
|
||||
src = ctx->getBelPinWire(driver.cell->bel, id_I);
|
||||
} else {
|
||||
src = ctx->getBelPinWire(driver.cell->bel, driver.port);
|
||||
|
|
@ -463,7 +400,7 @@ struct GowinGlobalRouter
|
|||
continue;
|
||||
}
|
||||
driver = net_before_dcs->driver;
|
||||
if (driver_is_buf(driver) || driver_is_clksrc(driver)) {
|
||||
if (gwu.driver_is_buf(driver) || gwu.driver_is_clksrc(driver)) {
|
||||
break;
|
||||
}
|
||||
net_before_dcs = nullptr;
|
||||
|
|
@ -472,7 +409,7 @@ struct GowinGlobalRouter
|
|||
|
||||
WireId src;
|
||||
// use BUF input if there is one
|
||||
if (driver_is_buf(driver)) {
|
||||
if (gwu.driver_is_buf(driver)) {
|
||||
src = ctx->getBelPinWire(driver.cell->bel, id_I);
|
||||
} else {
|
||||
src = ctx->getBelPinWire(driver.cell->bel, driver.port);
|
||||
|
|
@ -574,12 +511,14 @@ struct GowinGlobalRouter
|
|||
NPNR_ASSERT(net_before_dhcen != nullptr);
|
||||
|
||||
PortRef driver = net_before_dhcen->driver;
|
||||
NPNR_ASSERT_MSG(driver_is_buf(driver) || driver_is_clksrc(driver) || driver_is_mipi(driver),
|
||||
stringf("The input source for %s is not a clock.", ctx->nameOf(dhcen_ci)).c_str());
|
||||
NPNR_ASSERT_MSG(gwu.driver_is_buf(driver) || gwu.driver_is_clksrc(driver) || gwu.driver_is_mipi(driver),
|
||||
stringf("The input source (%s:%s) for %s is not a clock.", ctx->nameOf(driver.cell),
|
||||
driver.port.c_str(ctx), ctx->nameOf(dhcen_ci))
|
||||
.c_str());
|
||||
|
||||
IdString port;
|
||||
// use BUF input if there is one
|
||||
if (driver_is_buf(driver)) {
|
||||
if (gwu.driver_is_buf(driver)) {
|
||||
port = id_I;
|
||||
} else {
|
||||
port = driver.port;
|
||||
|
|
@ -588,7 +527,7 @@ struct GowinGlobalRouter
|
|||
|
||||
std::vector<PipId> path;
|
||||
RouteResult route_result;
|
||||
if (driver_is_mipi(driver)) {
|
||||
if (gwu.driver_is_mipi(driver)) {
|
||||
route_result = route_direct_net(
|
||||
net, [&](PipId pip, WireId src_wire) { return segment_wire_filter(pip) && dcs_input_filter(pip); },
|
||||
src, &path);
|
||||
|
|
@ -637,7 +576,7 @@ struct GowinGlobalRouter
|
|||
hw_dhcen->setAttr(id_DHCEN_USED, 1);
|
||||
dhcen_ci->copyPortTo(id_CE, hw_dhcen, id_CE);
|
||||
}
|
||||
if (driver_is_mipi(driver)) {
|
||||
if (gwu.driver_is_mipi(driver)) {
|
||||
ctx->bindWire(src, net_before_dhcen, STRENGTH_LOCKED);
|
||||
}
|
||||
|
||||
|
|
@ -673,7 +612,7 @@ struct GowinGlobalRouter
|
|||
return global_pip_filter(pip, src_wire) && segment_wire_filter(pip) && dcs_input_filter(pip);
|
||||
},
|
||||
src);
|
||||
if (route_result == NOT_ROUTED || route_result == ROUTED_PARTIALLY) {
|
||||
if (route_result == NOT_ROUTED) {
|
||||
log_error("Can't route the %s net. It might be worth removing the BUFG buffer flag.\n", ctx->nameOf(net));
|
||||
}
|
||||
|
||||
|
|
@ -1249,19 +1188,19 @@ struct GowinGlobalRouter
|
|||
}
|
||||
continue;
|
||||
}
|
||||
if (driver_is_buf(ni->driver)) {
|
||||
if (gwu.driver_is_buf(ni->driver)) {
|
||||
buf_nets.push_back(net.first);
|
||||
} else {
|
||||
if (driver_is_clksrc(ni->driver)) {
|
||||
if (gwu.driver_is_clksrc(ni->driver)) {
|
||||
clk_nets.push_back(net.first);
|
||||
} else {
|
||||
if (driver_is_dqce(ni->driver)) {
|
||||
if (gwu.driver_is_dqce(ni->driver)) {
|
||||
dqce_nets.push_back(net.first);
|
||||
} else {
|
||||
if (driver_is_dcs(ni->driver)) {
|
||||
if (gwu.driver_is_dcs(ni->driver)) {
|
||||
dcs_nets.push_back(net.first);
|
||||
} else {
|
||||
if (driver_is_dhcen(ni->driver)) {
|
||||
if (gwu.driver_is_dhcen(ni->driver)) {
|
||||
dhcen_nets.push_back(net.first);
|
||||
} else {
|
||||
seg_nets.push_back(net.first);
|
||||
|
|
|
|||
|
|
@ -209,6 +209,11 @@ void GowinImpl::init(Context *ctx)
|
|||
if (args.options.count("ioreg_in_iob")) {
|
||||
ctx->settings[id_IOREG_IN_IOB] = Property(1);
|
||||
}
|
||||
|
||||
// smart clock routing
|
||||
if (args.options.count("disable_gp_clock_routing")) {
|
||||
ctx->settings[id_NO_GP_CLOCK_ROUTING] = Property(1);
|
||||
}
|
||||
}
|
||||
|
||||
// We do not allow the use of global wires that bypass a special router.
|
||||
|
|
|
|||
|
|
@ -12,6 +12,75 @@
|
|||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
// clock sources
|
||||
bool GowinUtils::driver_is_clksrc(const PortRef &driver)
|
||||
{
|
||||
// dedicated pins
|
||||
if (CellTypePort(driver) == CellTypePort(id_IBUF, id_O)) {
|
||||
|
||||
NPNR_ASSERT(driver.cell->bel != BelId());
|
||||
IdStringList pin_func = get_pin_funcs(driver.cell->bel);
|
||||
for (size_t i = 0; i < pin_func.size(); ++i) {
|
||||
if (ctx->debug) {
|
||||
log_info("bel:%s, pin func: %zu:%s\n", ctx->nameOfBel(driver.cell->bel), i,
|
||||
pin_func[i].str(ctx).c_str());
|
||||
}
|
||||
if (pin_func[i].str(ctx).rfind("GCLKT", 0) == 0) {
|
||||
if (ctx->debug) {
|
||||
log_info("Clock pin:%s:%s\n", ctx->getBelName(driver.cell->bel).str(ctx).c_str(),
|
||||
pin_func[i].c_str(ctx));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// PLL outputs
|
||||
if (driver.cell->type.in(id_rPLL, id_PLLVR)) {
|
||||
if (driver.port.in(id_CLKOUT, id_CLKOUTD, id_CLKOUTD3, id_CLKOUTP)) {
|
||||
if (ctx->debug) {
|
||||
if (driver.cell->bel != BelId()) {
|
||||
log_info("PLL out bel:%s:%s\n", ctx->nameOfBel(driver.cell->bel), driver.port.c_str(ctx));
|
||||
} else {
|
||||
log_info("PLL out:%s:%s\n", ctx->nameOf(driver.cell), driver.port.c_str(ctx));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// HCLK outputs
|
||||
if (driver.cell->type.in(id_CLKDIV, id_CLKDIV2)) {
|
||||
if (driver.port.in(id_CLKOUT)) {
|
||||
if (ctx->debug) {
|
||||
if (driver.cell->bel != BelId()) {
|
||||
log_info("%s out bel:%s:%s:%s\n", driver.cell->type.c_str(ctx),
|
||||
ctx->getBelName(driver.cell->bel).str(ctx).c_str(), driver.port.c_str(ctx),
|
||||
ctx->nameOfWire(ctx->getBelPinWire(driver.cell->bel, driver.port)));
|
||||
} else {
|
||||
log_info("%s out:%s:%s\n", driver.cell->type.c_str(ctx), ctx->nameOf(driver.cell),
|
||||
driver.port.c_str(ctx));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// DLLDLY outputs
|
||||
if (driver.cell->type == id_DLLDLY) {
|
||||
if (driver.port.in(id_CLKOUT)) {
|
||||
if (ctx->debug) {
|
||||
if (driver.cell->bel != BelId()) {
|
||||
log_info("%s out bel:%s:%s\n", driver.cell->type.c_str(ctx),
|
||||
ctx->getBelName(driver.cell->bel).str(ctx).c_str(), driver.port.c_str(ctx));
|
||||
} else {
|
||||
log_info("%s out:%s:%s\n", driver.cell->type.c_str(ctx), ctx->nameOf(driver.cell),
|
||||
driver.port.c_str(ctx));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Segments
|
||||
int GowinUtils::get_segments_count(void) const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -67,6 +67,24 @@ struct GowinUtils
|
|||
return ni->users.entries() != 0;
|
||||
}
|
||||
|
||||
// net sources
|
||||
inline bool driver_is_io(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_IBUF, id_O); }
|
||||
inline bool driver_is_buf(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_BUFG, id_O); }
|
||||
inline bool driver_is_dqce(const PortRef &driver)
|
||||
{
|
||||
return CellTypePort(driver) == CellTypePort(id_DQCE, id_CLKOUT);
|
||||
}
|
||||
inline bool driver_is_dcs(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DCS, id_CLKOUT); }
|
||||
inline bool driver_is_dhcen(const PortRef &driver)
|
||||
{
|
||||
return CellTypePort(driver) == CellTypePort(id_DHCEN, id_CLKOUT);
|
||||
}
|
||||
inline bool driver_is_mipi(const PortRef &driver)
|
||||
{
|
||||
return CellTypePort(driver) == CellTypePort(id_IOBUF, id_O) && driver.cell->params.count(id_MIPI_IBUF);
|
||||
}
|
||||
bool driver_is_clksrc(const PortRef &driver);
|
||||
|
||||
// BSRAM
|
||||
bool has_SP32(void);
|
||||
bool need_SP_fix(void);
|
||||
|
|
|
|||
|
|
@ -3860,10 +3860,34 @@ struct GowinPacker
|
|||
log_info("Pack buffered nets...\n");
|
||||
|
||||
for (auto &net : ctx->nets) {
|
||||
auto &ni = *net.second;
|
||||
if (ni.driver.cell == nullptr || ni.attrs.count(id_CLOCK) == 0 || ni.users.empty()) {
|
||||
NetInfo *ni = net.second.get();
|
||||
if (ni->driver.cell == nullptr || ni->users.empty() || net.first == ctx->id("$PACKER_GND") ||
|
||||
net.first == ctx->id("$PACKER_VCC")) {
|
||||
continue;
|
||||
}
|
||||
if (ni->attrs.count(id_CLOCK) == 0) {
|
||||
if (ctx->settings.count(id_NO_GP_CLOCK_ROUTING)) {
|
||||
continue;
|
||||
}
|
||||
if (gwu.driver_is_clksrc(ni->driver) || (!gwu.driver_is_io(ni->driver))) {
|
||||
// no need for buffering
|
||||
continue;
|
||||
}
|
||||
// check users for the clock inputs
|
||||
bool has_clock_users = false;
|
||||
for (auto usr : ni->users) {
|
||||
if (usr.port.in(id_CLKIN, id_CLK, id_CLK0, id_CLK1, id_CLK2, id_CLK3, id_CLKFB)) {
|
||||
has_clock_users = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_clock_users) {
|
||||
continue;
|
||||
}
|
||||
if (ctx->verbose) {
|
||||
log_info("Add buffering to a potentially clock network '%s'\n", ctx->nameOf(ni));
|
||||
}
|
||||
}
|
||||
|
||||
// make new BUF cell single user for the net driver
|
||||
IdString buf_name = ctx->idf("%s_BUFG", net.first.c_str(ctx));
|
||||
|
|
@ -3871,8 +3895,8 @@ struct GowinPacker
|
|||
CellInfo *buf_ci = ctx->cells.at(buf_name).get();
|
||||
buf_ci->addInput(id_I);
|
||||
// move driver
|
||||
CellInfo *driver_cell = ni.driver.cell;
|
||||
IdString driver_port = ni.driver.port;
|
||||
CellInfo *driver_cell = ni->driver.cell;
|
||||
IdString driver_port = ni->driver.port;
|
||||
|
||||
driver_cell->movePortTo(driver_port, buf_ci, id_O);
|
||||
buf_ci->connectPorts(id_I, driver_cell, driver_port);
|
||||
|
|
|
|||
Loading…
Reference in New Issue