Gowin. Add HCLK wires to PLL. (#1462)

Adds the ability to use high-speed clock lines (together with CLKDIV2
type frequency dividers operating on them) as sieve signals for the
CLKIN and CLKFB inputs of the rPLL and PLLVR primitives (these cover the
full range of supported Gowin chips).

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
This commit is contained in:
YRabbit 2025-03-12 18:32:38 +10:00 committed by GitHub
parent 0492c55efd
commit d8988e1682
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 146 additions and 43 deletions

View File

@ -282,8 +282,9 @@ struct GowinGlobalRouter
if (driver.cell->type.in(id_CLKDIV, id_CLKDIV2)) {
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));
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;
}

View File

@ -287,7 +287,7 @@ void GowinImpl::adjust_dsp_pin_mapping(void)
/*
Each HCLK section can serve one of three purposes:
1. A simple routing path to IOLOGIC FCLK
1. A simple routing path to IOLOGIC FCLK or PLL inputs
2. CLKDIV2
3. CLKDIV (only one section at any time)
@ -305,6 +305,45 @@ void GowinImpl::place_constrained_hclk_cells()
std::vector<std::pair<IdString, int>> alias_cells;
std::map<std::pair<IdString, int>, BelId> final_placement;
const bool chip_has_clkdiv_hclk_connection = gwu.has_CLKDIV_HCLK();
const bool chip_has_pll_hclk = gwu.has_PLL_HCLK();
pool<BelId> free_pll_bels;
if (chip_has_pll_hclk) {
// gather free PLL bels
for (auto bel : ctx->getBels()) {
if (ctx->getBelType(bel).in(id_rPLL, id_PLLVR) && !ctx->getBoundBelCell(bel)) {
free_pll_bels.insert(bel);
}
}
}
const auto is_hclk_user = [&](const CellInfo *ci) -> bool {
if ((is_iologici(ci) || is_iologico(ci)) &&
!ci->type.in(id_ODDR, id_ODDRC, id_IDDR, id_IDDRC, id_IOLOGICI_EMPTY, id_IOLOGICO_EMPTY)) {
return true;
}
if (chip_has_pll_hclk && is_pll(ci)) {
return true;
}
return false;
};
// returns the list of networks connected to the cell
std::vector<const NetInfo *> net_list;
auto get_nets = [&](const CellInfo *ci) -> void {
net_list.clear();
if (is_iologici(ci) || is_iologico(ci)) {
net_list.push_back(ci->getPort(id_FCLK));
return;
}
if (chip_has_pll_hclk && is_pll(ci)) {
net_list.push_back(ci->getPort(id_CLKIN));
net_list.push_back(ci->getPort(id_CLKFB));
return;
}
};
// cell, port
std::set<IdString> seen_hclk_users;
for (auto &cell : ctx->cells) {
auto ci = cell.second.get();
@ -319,51 +358,57 @@ void GowinImpl::place_constrained_hclk_cells()
if ((seen_hclk_users.find(ci->name) != seen_hclk_users.end()))
continue;
if (((is_iologici(ci) || is_iologico(ci)) &&
!ci->type.in(id_ODDR, id_ODDRC, id_IDDR, id_IDDRC, id_IOLOGICI_EMPTY, id_IOLOGICO_EMPTY))) {
NetInfo *hclk_net = ci->getPort(id_FCLK);
if (hclk_net)
continue;
CellInfo *hclk_driver = hclk_net->driver.cell;
if (!hclk_driver)
continue;
if (chip.str(ctx) == "GW1N-9C" && hclk_driver->type != id_CLKDIV2) {
// CLKDIV doesn't seem to connect directly to FCLK on this device, and routing is guaranteed to succeed.
continue;
}
int alias_count = 0;
std::set<std::set<BelId>> seen_options;
for (auto user : hclk_net->users) {
std::vector<BelId> bel_candidates;
std::set<BelId> these_options;
if (!(user.port == id_FCLK && (is_iologici(user.cell) || is_iologico(user.cell)) &&
!user.cell->type.in(id_ODDR, id_ODDRC, id_IDDR, id_IDDRC)))
if (is_hclk_user(ci)) {
get_nets(ci);
for (auto &hclk_net : net_list) {
if (!hclk_net) {
continue;
if (seen_hclk_users.find(user.cell->name) != seen_hclk_users.end())
}
CellInfo *hclk_driver = hclk_net->driver.cell;
if (!hclk_driver)
continue;
if (!(chip_has_clkdiv_hclk_connection || hclk_driver->type == id_CLKDIV2)) {
continue;
seen_hclk_users.insert(user.cell->name);
if (ctx->debug) {
log_info("Custom HCLK Placer: Found HCLK user: %s\n", user.cell->name.c_str(ctx));
}
gwu.find_connected_bels(user.cell, id_FCLK, id_CLKDIV2, id_CLKOUT, 16, bel_candidates);
these_options.insert(bel_candidates.begin(), bel_candidates.end());
int alias_count = 0;
std::set<std::set<BelId>> seen_options;
for (auto user : hclk_net->users) {
std::vector<BelId> bel_candidates;
std::set<BelId> these_options;
if (seen_options.find(these_options) != seen_options.end())
continue;
seen_options.insert(these_options);
if (!is_hclk_user(user.cell)) {
continue;
}
seen_hclk_users.insert(user.cell->name);
// When an HCLK signal is routed to different (and disconnected) FCLKs, we treat each new
// HCLK-FCLK connection as a pseudo-HCLK cell since it must also be assigned an HCLK section
auto alias_index = std::pair<IdString, int>(hclk_driver->name, alias_count);
alias_cells.push_back(alias_index);
alias_count++;
if (ctx->debug) {
log_info("Custom HCLK Placer: Found HCLK user: %s\n", user.cell->name.c_str(ctx));
}
for (auto option : these_options) {
bel_cell_map[option].insert(alias_index);
if (is_pll(user.cell) && user.cell->bel == BelId()) {
if (free_pll_bels.empty()) {
log_error("No BELs for %s\n", ctx->nameOf(user.cell));
}
ctx->bindBel(free_pll_bels.pop(), user.cell, PlaceStrength::STRENGTH_LOCKED);
}
gwu.find_connected_bels(user.cell, user.port, id_CLKDIV2, id_CLKOUT, 1000000, bel_candidates);
these_options.insert(bel_candidates.begin(), bel_candidates.end());
if (seen_options.find(these_options) != seen_options.end())
continue;
seen_options.insert(these_options);
// When an HCLK signal is routed to different (and disconnected) FCLKs, we treat each new
// HCLK-FCLK connection as a pseudo-HCLK cell since it must also be assigned an HCLK section
auto alias_index = std::pair<IdString, int>(hclk_driver->name, alias_count);
alias_cells.push_back(alias_index);
alias_count++;
for (auto option : these_options) {
bel_cell_map[option].insert(alias_index);
}
}
}
}
@ -571,8 +616,6 @@ void GowinImpl::postRoute()
}
user.cell->setAttr(id_IOLOGIC_FCLK, Property("UNKNOWN"));
visited_hclk_users.insert(user.cell->name);
// XXX Based on the implementation, perhaps a function
// is needed to get Pip from a Wire
PipId up_pip = h_net->wires.at(ctx->getNetinfoSinkWire(h_net, user, 0)).pip;
IdString up_wire_name = ctx->getWireName(ctx->getPipSrcWire(up_pip))[1];
if (up_wire_name.in(id_HCLK_OUT0, id_HCLK_OUT1, id_HCLK_OUT2, id_HCLK_OUT3)) {
@ -590,6 +633,39 @@ void GowinImpl::postRoute()
}
}
}
} else {
if (is_pll(ci)) {
// CLKIN is connected to HLCK?
NetInfo *h_net = ci->getPort(id_CLKIN);
if (h_net == nullptr || h_net->wires.empty()) {
continue;
}
PortRef pr = {ci, id_CLKIN};
PipId up_pip = h_net->wires.at(ctx->getNetinfoSinkWire(h_net, pr, 0)).pip;
IdString up_wire_name = ctx->getWireName(ctx->getPipSrcWire(up_pip))[1];
if (up_wire_name.in(id_HCLK_OUT0, id_HCLK_OUT1)) {
ci->setParam(id_INSEL, Property("CLKIN3"));
} else {
if (up_wire_name.in(id_HCLK_OUT2, id_HCLK_OUT3)) {
ci->setParam(id_INSEL, Property("CLKIN4"));
}
}
// CLKFB is connected to HLCK?
h_net = ci->getPort(id_CLKFB);
if (h_net == nullptr || h_net->wires.empty()) {
continue;
}
pr.port = id_CLKFB;
up_pip = h_net->wires.at(ctx->getNetinfoSinkWire(h_net, pr, 0)).pip;
up_wire_name = ctx->getWireName(ctx->getPipSrcWire(up_pip))[1];
if (up_wire_name.in(id_HCLK_OUT0, id_HCLK_OUT1)) {
ci->setParam(id_FBSEL, Property("CLKFB1"));
} else {
if (up_wire_name.in(id_HCLK_OUT2, id_HCLK_OUT3)) {
ci->setParam(id_FBSEL, Property("CLKFB4"));
}
}
}
}
}
}

View File

@ -93,6 +93,10 @@ inline bool type_is_userflash(IdString cell_type)
}
inline bool is_userflash(const CellInfo *cell) { return type_is_userflash(cell->type); }
// Return true if a cell is a PLL
inline bool type_is_pll(IdString cell_type) { return cell_type.in(id_rPLL, id_PLLVR); }
inline bool is_pll(const CellInfo *cell) { return type_is_pll(cell->type); }
// Return true if a cell is a EMCU
inline bool type_is_emcu(IdString cell_type) { return cell_type == id_EMCU; }
inline bool is_emcu(const CellInfo *cell) { return type_is_emcu(cell->type); }
@ -168,6 +172,8 @@ NPNR_PACKED_STRUCT(struct Extra_chip_data_POD {
static constexpr int32_t NEED_BSRAM_OUTREG_FIX = 4;
static constexpr int32_t NEED_BLKSEL_FIX = 8;
static constexpr int32_t HAS_BANDGAP = 16;
static constexpr int32_t HAS_PLL_HCLK = 32;
static constexpr int32_t HAS_CLKDIV_HCLK = 64;
});
} // namespace

View File

@ -20,6 +20,8 @@ CHIP_NEED_SP_FIX = 0x2
CHIP_NEED_BSRAM_OUTREG_FIX = 0x4
CHIP_NEED_BLKSEL_FIX = 0x8
CHIP_HAS_BANDGAP = 0x10
CHIP_HAS_PLL_HCLK = 0x20
CHIP_HAS_CLKDIV_HCLK = 0x40
# Tile flags
TILE_I3C_CAPABLE_IO = 0x1
@ -1442,6 +1444,10 @@ def main():
chip_flags |= CHIP_NEED_BLKSEL_FIX;
if "HAS_BANDGAP" in db.chip_flags:
chip_flags |= CHIP_HAS_BANDGAP;
if "HAS_PLL_HCLK" in db.chip_flags:
chip_flags |= CHIP_HAS_PLL_HCLK;
if "HAS_CLKDIV_HCLK" in db.chip_flags:
chip_flags |= CHIP_HAS_CLKDIV_HCLK;
X = db.cols;
Y = db.rows;

View File

@ -190,6 +190,18 @@ bool GowinUtils::need_BLKSEL_fix(void)
return extra->chip_flags & Extra_chip_data_POD::NEED_BLKSEL_FIX;
}
bool GowinUtils::has_PLL_HCLK(void)
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
return extra->chip_flags & Extra_chip_data_POD::HAS_PLL_HCLK;
}
bool GowinUtils::has_CLKDIV_HCLK(void)
{
const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
return extra->chip_flags & Extra_chip_data_POD::HAS_CLKDIV_HCLK;
}
IdString GowinUtils::create_aux_name(IdString main_name, int idx, const char *str_suffix)
{
return idx ? ctx->idf("%s%s%d", main_name.c_str(ctx), str_suffix, idx)

View File

@ -57,6 +57,8 @@ struct GowinUtils
bool need_SP_fix(void);
bool need_BSRAM_OUTREG_fix(void);
bool need_BLKSEL_fix(void);
bool has_PLL_HCLK(void);
bool has_CLKDIV_HCLK(void);
// Power saving
bool has_BANDGAP(void);