Himbaechel Gowin: HCLK Support (#1340)
* Himbaechel Gowin: Add support for CLKDIV and CLKDIV2 * Himbaechel Gowin: Add support for CLKDIV and CLKDIV2 * Gowin Himbaechel: HCLK Bug fixes and corrections
This commit is contained in:
parent
11d335c7ce
commit
e9e7dce23d
@ -1239,3 +1239,16 @@ X(DQCE_PIP)
|
||||
X(DCS_USED)
|
||||
X(SELFORCE)
|
||||
|
||||
//HCLK Bels
|
||||
X(CLKDIV)
|
||||
X(CLKDIV2)
|
||||
|
||||
//HCLK Ports
|
||||
X(HCLKIN)
|
||||
X(RESETN)
|
||||
// X(CALIB)
|
||||
// X(CLKOUT)
|
||||
|
||||
//HCLK Parameters
|
||||
X(DIV_MODE)
|
||||
X(GSREN)
|
||||
|
@ -51,6 +51,27 @@ struct GowinCstReader
|
||||
return Loc(col - 1, row - 1, z);
|
||||
}
|
||||
|
||||
BelId getConstrainedHCLKBel(std::smatch match, int maxX, int maxY)
|
||||
{
|
||||
int idx = std::stoi(match[3]);
|
||||
int bel_z = BelZ::CLKDIV_0_Z + 2 * idx;
|
||||
|
||||
std::string side = match[2].str();
|
||||
bool lr = (side == "LEFT") || (side == "RIGHT");
|
||||
int y_coord = (side == "BOTTOM") ? maxY - 1 : 0;
|
||||
int x_coord = (side == "RIGHT") ? maxX - 1 : 0;
|
||||
|
||||
for (auto &bel : ctx->getBelsInBucket(ctx->getBelBucketForCellType(id_CLKDIV))) {
|
||||
auto this_loc = ctx->getBelLocation(bel);
|
||||
if (lr && this_loc.x == x_coord && this_loc.z == bel_z && this_loc.y != 0 &&
|
||||
this_loc.y != maxY - 1) // left or right side
|
||||
return bel;
|
||||
else if (!lr && this_loc.y == y_coord && this_loc.z == bel_z) // top or bottom side
|
||||
return bel;
|
||||
}
|
||||
return BelId();
|
||||
}
|
||||
|
||||
bool run(void)
|
||||
{
|
||||
pool<std::pair<IdString, IdStringList>> constrained_cells;
|
||||
@ -71,15 +92,19 @@ struct GowinCstReader
|
||||
std::regex iobelre = std::regex("IO([TRBL])([0-9]+)\\[?([A-Z])\\]?");
|
||||
std::regex inslocre =
|
||||
std::regex("INS_LOC +\"([^\"]+)\" +R([0-9]+)C([0-9]+)\\[([0-9])\\]\\[([AB])\\] *;.*[\\s\\S]*");
|
||||
std::regex hclkre =
|
||||
std::regex("INS_LOC +\"([^\"]+)\" +(TOP|RIGHT|BOTTOM|LEFT)SIDE\\[([0,1])\\] *;*[\\s\\S]*");
|
||||
std::regex clockre = std::regex("CLOCK_LOC +\"([^\"]+)\" +BUF([GS])(\\[([0-7])\\])?[^;]*;.*[\\s\\S]*");
|
||||
std::smatch match, match_attr, match_pinloc;
|
||||
std::string line, pinline;
|
||||
std::vector<IdStringList> constrained_clkdivs;
|
||||
enum
|
||||
{
|
||||
ioloc,
|
||||
ioport,
|
||||
insloc,
|
||||
clock
|
||||
clock,
|
||||
hclk
|
||||
} cst_type;
|
||||
|
||||
while (!in.eof()) {
|
||||
@ -95,10 +120,14 @@ struct GowinCstReader
|
||||
if (std::regex_match(line, match, inslocre)) {
|
||||
cst_type = insloc;
|
||||
} else {
|
||||
if ((!line.empty()) && (line.rfind("//", 0) == std::string::npos)) {
|
||||
log_warning("Invalid constraint: %s\n", line.c_str());
|
||||
if (std::regex_match(line, match, hclkre)) {
|
||||
cst_type = hclk;
|
||||
} else {
|
||||
if ((!line.empty()) && (line.rfind("//", 0) == std::string::npos)) {
|
||||
log_warning("Invalid constraint: %s\n", line.c_str());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -166,6 +195,27 @@ struct GowinCstReader
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case hclk: {
|
||||
IdString cell_type = it->second->type;
|
||||
if (cell_type != id_CLKDIV) {
|
||||
log_error("Unsupported or invalid cell type %s for hclk\n", cell_type.c_str(ctx));
|
||||
}
|
||||
BelId hclk_bel = getConstrainedHCLKBel(match, ctx->getGridDimX(), ctx->getGridDimY());
|
||||
if (hclk_bel != BelId()) {
|
||||
auto hclk_bel_name = ctx->getBelName(hclk_bel);
|
||||
if (std::find(constrained_clkdivs.begin(), constrained_clkdivs.end(), hclk_bel_name) !=
|
||||
constrained_clkdivs.end()) {
|
||||
log_error("Only one CLKDIV can be placed at %sSIDE[%s]\n", match[2].str().c_str(),
|
||||
match[3].str().c_str());
|
||||
}
|
||||
constrained_clkdivs.push_back(hclk_bel_name);
|
||||
it->second->setAttr(id_BEL, ctx->getBelName(hclk_bel).str(ctx));
|
||||
debug_cell(it->second->name, ctx->getBelName(hclk_bel));
|
||||
} else {
|
||||
log_error("No Bel of type CLKDIV found at constrained location %sSIDE[%s]\n",
|
||||
match[2].str().c_str(), match[3].str().c_str());
|
||||
}
|
||||
} break;
|
||||
default: { // IO_PORT attr=value
|
||||
std::string attr_val = match[2];
|
||||
while (std::regex_match(attr_val, match_attr, port_attrre)) {
|
||||
|
@ -256,6 +256,16 @@ struct GowinGlobalRouter
|
||||
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\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;
|
||||
}
|
||||
|
||||
|
@ -71,6 +71,9 @@ struct GowinImpl : HimbaechelAPI
|
||||
std::vector<GowinCellInfo> fast_cell_info;
|
||||
void assign_cell_info();
|
||||
|
||||
// Remember HCLK sections that have been reserved to route HCLK signals
|
||||
std::set<BelId> routing_reserved_hclk_sections;
|
||||
|
||||
// dsp control nets
|
||||
// Each DSP and each macro has a small set of control wires that are
|
||||
// allocated to internal primitives as needed. It is assumed that most
|
||||
@ -87,9 +90,14 @@ struct GowinImpl : HimbaechelAPI
|
||||
// since this information is already lost during unbinding
|
||||
void adjust_dsp_pin_mapping(void);
|
||||
|
||||
// Place explicityl constrained or implicitly constrained (by IOLOGIC) CLKDIV and CLKDIV2 cells
|
||||
// to avoid routing conflicts and maximize utilization
|
||||
void place_constrained_hclk_cells();
|
||||
|
||||
// bel placement validation
|
||||
bool slice_valid(int x, int y, int z) const;
|
||||
bool dsp_valid(Loc l, IdString bel_type, bool explain_invalid) const;
|
||||
bool hclk_valid(BelId bel, IdString bel_type) const;
|
||||
};
|
||||
|
||||
struct GowinArch : HimbaechelArch
|
||||
@ -259,7 +267,250 @@ void GowinImpl::adjust_dsp_pin_mapping(void)
|
||||
}
|
||||
}
|
||||
|
||||
void GowinImpl::prePlace() { assign_cell_info(); }
|
||||
/*
|
||||
Each HCLK section can serve one of three purposes:
|
||||
1. A simple routing path to IOLOGIC FCLK
|
||||
2. CLKDIV2
|
||||
3. CLKDIV (only one section at any time)
|
||||
|
||||
Our task is to distribute HCLK signal providers to sections in a way that maximizes utilization while
|
||||
enforcing user constraints on CLKDIV placement. We achieve this by solving two bipartite matchings:
|
||||
- The first determines the best HCLK to place a CLKDIV within the established graph. This is then refined
|
||||
to determine what section to assign the CLKDIV to based on what IOLOGIC it connects to
|
||||
- The second determines which HCLK sections to use as CLKDIV2 or to reserve for routing.
|
||||
*/
|
||||
void GowinImpl::place_constrained_hclk_cells()
|
||||
{
|
||||
log_info("Running custom HCLK placer...\n");
|
||||
std::map<IdStringList, IdString> constrained_clkdivs;
|
||||
std::map<BelId, std::set<std::pair<IdString, int>>> bel_cell_map;
|
||||
std::vector<std::pair<IdString, int>> alias_cells;
|
||||
std::map<std::pair<IdString, int>, BelId> final_placement;
|
||||
|
||||
std::set<IdString> seen_hclk_users;
|
||||
for (auto &cell : ctx->cells) {
|
||||
auto ci = cell.second.get();
|
||||
|
||||
if (is_clkdiv(ci) && ci->attrs.count(id_BEL)) {
|
||||
BelId constrained_bel = ctx->getBelByName(IdStringList::parse(ctx, ci->attrs.at(id_BEL).as_string()));
|
||||
NPNR_ASSERT(constrained_bel != BelId() && ctx->getBelType(constrained_bel) == id_CLKDIV);
|
||||
auto hclk_id_loc = gwu.get_hclk_id(constrained_bel);
|
||||
constrained_clkdivs[hclk_id_loc] = ci->name;
|
||||
}
|
||||
|
||||
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))) {
|
||||
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)))
|
||||
continue;
|
||||
if (seen_hclk_users.find(user.cell->name) != seen_hclk_users.end())
|
||||
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());
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First matching. We use the upper CLKDIV2 as the ID for an HCLK
|
||||
std::map<IdStringList, std::set<IdString>> clkdiv_graph;
|
||||
for (auto bel_cell_candidates : bel_cell_map) {
|
||||
auto bel = bel_cell_candidates.first;
|
||||
auto hclk_id_loc = gwu.get_hclk_id(bel);
|
||||
if (constrained_clkdivs.find(hclk_id_loc) != constrained_clkdivs.end()) {
|
||||
continue;
|
||||
}
|
||||
for (auto candidate : bel_cell_candidates.second) {
|
||||
auto ci = ctx->cells.at(candidate.first).get();
|
||||
if ((ci->type != id_CLKDIV) || ci->attrs.count(id_BEL)) {
|
||||
continue;
|
||||
}
|
||||
clkdiv_graph[hclk_id_loc].insert(candidate.first);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->debug) {
|
||||
log_info("<-----CUSTOM HCLK PLACER: Constrained CLKDIVs----->\n");
|
||||
for (auto match_pair : constrained_clkdivs) {
|
||||
log_info("%s cell <-----> CLKDIV at HCLK %s\n", match_pair.second.c_str(ctx), match_pair.second.c_str(ctx));
|
||||
}
|
||||
log("\n");
|
||||
}
|
||||
|
||||
auto matching = gwu.find_maximum_bipartite_matching<IdStringList, IdString>(
|
||||
clkdiv_graph); // these will serve as constraints
|
||||
constrained_clkdivs.insert(matching.begin(), matching.end());
|
||||
|
||||
if (ctx->debug) {
|
||||
log_info("<-----CUSTOM HCLK PLACER: First Matching(CLKDIV) Results----->\n");
|
||||
for (auto match_pair : matching) {
|
||||
log_info("%s cell <-----> CLKDIV at HCLK %s\n", match_pair.second.c_str(ctx), match_pair.second.c_str(ctx));
|
||||
}
|
||||
log("\n");
|
||||
}
|
||||
|
||||
// Refine matching to HCLK section, based on what connections actually exist
|
||||
std::map<IdString, std::pair<IdString, int>> true_clkdivs;
|
||||
std::set<BelId> used_bels;
|
||||
for (auto constr_pair : constrained_clkdivs) {
|
||||
BelId option0 = ctx->getBelByName(constr_pair.first);
|
||||
BelId option1 = gwu.get_other_hclk_clkdiv2(option0);
|
||||
|
||||
// On the GW1N-9 devices, only the lower CLKDIV can be fed by a CLKDIV2
|
||||
std::vector<BelId> options = {option1, option0};
|
||||
if (chip.str(ctx) == "GW1N-9C") {
|
||||
auto ci = ctx->cells.at(constr_pair.second).get();
|
||||
for (auto cluster_child_cell : ci->constr_children)
|
||||
if (cluster_child_cell->type == id_CLKDIV2 && options.back() == option0) {
|
||||
options.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool placed = false;
|
||||
for (auto option : options) {
|
||||
if (placed || (used_bels.find(option) != used_bels.end()))
|
||||
continue;
|
||||
for (auto option_cell : bel_cell_map[option]) {
|
||||
if ((option_cell.first != constr_pair.second) ||
|
||||
(true_clkdivs.find(option_cell.first) != true_clkdivs.end()))
|
||||
continue;
|
||||
final_placement[option_cell] = option;
|
||||
true_clkdivs[option_cell.first] = option_cell;
|
||||
used_bels.insert(option);
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// This must be a constrained CLKDIV that either does not serve IOLOGIC Or
|
||||
// does not have a direct (HCLK-FCLK) connection IOLOGIC it serves
|
||||
// We create a new alias to represent this
|
||||
if (!placed) {
|
||||
auto new_alias = std::pair<IdString, int>(constr_pair.second, -1);
|
||||
for (auto option : options)
|
||||
bel_cell_map[option].insert(new_alias);
|
||||
alias_cells.push_back(new_alias);
|
||||
true_clkdivs[constr_pair.second] = new_alias;
|
||||
}
|
||||
}
|
||||
|
||||
// Second Matching for CLKDIV2 and routing reservation
|
||||
std::map<IdStringList, std::set<std::pair<IdString, int>>> full_hclk_graph;
|
||||
for (auto bel_cell_candidates : bel_cell_map) {
|
||||
auto bel = bel_cell_candidates.first;
|
||||
auto bel_name = ctx->getBelName(bel);
|
||||
if (!used_bels.count(bel)) {
|
||||
for (auto candidate : bel_cell_candidates.second) {
|
||||
if (((candidate.second == -1) || (!true_clkdivs.count(candidate.first)) ||
|
||||
!(true_clkdivs[candidate.first] == candidate))) {
|
||||
full_hclk_graph[bel_name].insert(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto full_matching = gwu.find_maximum_bipartite_matching(full_hclk_graph);
|
||||
for (auto belname_cellalias : full_matching) {
|
||||
auto bel = ctx->getBelByName(belname_cellalias.first);
|
||||
NPNR_ASSERT(!used_bels.count(bel));
|
||||
final_placement[belname_cellalias.second] = bel;
|
||||
}
|
||||
|
||||
if (ctx->debug) {
|
||||
log_info("<-----CUSTOM HCLK PLACER: Second Matching(CLKDIV2 and Routing) Results------>\n");
|
||||
for (auto match_pair : full_matching) {
|
||||
auto alias = match_pair.second;
|
||||
auto bel = match_pair.first;
|
||||
auto cell_type = ctx->cells.at(alias.first).get()->type;
|
||||
log_info("%s cell %s Alias %d <-----> HCLK Section at %s\n", cell_type.c_str(ctx), alias.first.c_str(ctx),
|
||||
alias.second, bel.str(ctx).c_str());
|
||||
}
|
||||
log("\n");
|
||||
}
|
||||
|
||||
for (auto cell_alias : alias_cells) {
|
||||
auto ci = ctx->cells.at(cell_alias.first).get();
|
||||
|
||||
if (final_placement.find(cell_alias) == final_placement.end() && ctx->debug)
|
||||
if (ci->type == id_CLKDIV2 || ci->type == id_CLKDIV)
|
||||
log_info("Custom HCLK Placer: Unable to place HCLK cell %s; no BELs available to implement cell type "
|
||||
"%s\n",
|
||||
ci->name.c_str(ctx), ci->type.c_str(ctx));
|
||||
else
|
||||
log_info("Custom HCLK Placer: Unable to guarantee route for HCLK signal from %s to IOLOGIC\n",
|
||||
ci->name.c_str(ctx));
|
||||
|
||||
else {
|
||||
auto placement = final_placement[cell_alias];
|
||||
if (ctx->debug)
|
||||
log_info("Custom HCLK Placer: Placing %s Alias %d at %s\n", cell_alias.first.c_str(ctx),
|
||||
cell_alias.second, ctx->nameOfBel(placement));
|
||||
if (ci->type == id_CLKDIV2)
|
||||
ctx->bindBel(placement, ci, STRENGTH_LOCKED);
|
||||
|
||||
else if ((ci->type == id_CLKDIV) && (true_clkdivs[cell_alias.first] == cell_alias)) {
|
||||
NetInfo *in = ci->getPort(id_HCLKIN);
|
||||
if (in && in->driver.cell->type == id_CLKDIV2) {
|
||||
ctx->bindBel(placement, in->driver.cell, STRENGTH_LOCKED);
|
||||
}
|
||||
auto clkdiv_bel = gwu.get_clkdiv_for_clkdiv2(placement);
|
||||
ctx->bindBel(clkdiv_bel, ci, STRENGTH_LOCKED);
|
||||
} else {
|
||||
if (ctx->debug)
|
||||
log_info("Custom HCLK Placer: Reserving HCLK %s to route clock from %s\n",
|
||||
ctx->nameOfBel(placement), ci->name.c_str(ctx));
|
||||
routing_reserved_hclk_sections.insert(placement);
|
||||
}
|
||||
}
|
||||
if (ci->attrs.count(id_BEL))
|
||||
ci->unsetAttr(id_BEL);
|
||||
}
|
||||
}
|
||||
|
||||
void GowinImpl::prePlace()
|
||||
{
|
||||
place_constrained_hclk_cells();
|
||||
assign_cell_info();
|
||||
}
|
||||
|
||||
void GowinImpl::postPlace()
|
||||
{
|
||||
gwu.has_SP32();
|
||||
@ -349,6 +600,9 @@ bool GowinImpl::isBelLocationValid(BelId bel, bool explain_invalid) const
|
||||
case ID_MULT36X36: /* fall-through */
|
||||
case ID_ALU54D:
|
||||
return dsp_valid(l, bel_type, explain_invalid);
|
||||
case ID_CLKDIV2: /* fall-through */
|
||||
case ID_CLKDIV:
|
||||
return hclk_valid(bel, bel_type);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -608,6 +862,52 @@ bool GowinImpl::slice_valid(int x, int y, int z) const
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
Every HCLK section can be used in one of 3 ways:
|
||||
1. As a simple routing path to IOLOGIC FCLK
|
||||
2. As a CLKDIV2
|
||||
3. As a CLKDIV (potentially fed by the CLKDIV2 in its section)
|
||||
|
||||
Here we validate that the placement of cells fits within these 3 use cases, while ensuring that
|
||||
we enforce the constraint that only 1 CLKDIV can be used per HCLK (there is only 1 CLKDIV in each
|
||||
HCLK but we pretend there are two because doing so makes it easier to enforce the real constraint
|
||||
that HCLK signals don't crisscross between HCLK sections even after "transformation" by a CLKDIV
|
||||
or CLKDIV2)
|
||||
*/
|
||||
bool GowinImpl::hclk_valid(BelId bel, IdString bel_type) const
|
||||
{
|
||||
if (bel_type == id_CLKDIV2) {
|
||||
if (routing_reserved_hclk_sections.count(bel))
|
||||
return false;
|
||||
auto clkdiv_cell = ctx->getBoundBelCell(gwu.get_clkdiv_for_clkdiv2(bel));
|
||||
if (clkdiv_cell && ctx->getBoundBelCell(bel)->cluster != clkdiv_cell->name)
|
||||
return false;
|
||||
return true;
|
||||
} else if (bel_type == id_CLKDIV) {
|
||||
BelId clkdiv2_bel = gwu.get_clkdiv2_for_clkdiv(bel);
|
||||
if (routing_reserved_hclk_sections.count(clkdiv2_bel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto other_clkdiv_cell = ctx->getBoundBelCell(gwu.get_other_hclk_clkdiv(bel));
|
||||
if (other_clkdiv_cell)
|
||||
return false;
|
||||
|
||||
auto clkdiv2_bel_cell = ctx->getBoundBelCell(clkdiv2_bel);
|
||||
if (clkdiv2_bel_cell && clkdiv2_bel_cell->cluster != ctx->getBoundBelCell(bel)->name)
|
||||
return false;
|
||||
|
||||
if (clkdiv2_bel_cell && chip.str(ctx) == "GW1N-9C") {
|
||||
// On the GW1N(R)-9C, it appears that only the 'odd' CLKDIV2 is connected to CLKDIV
|
||||
Loc loc = ctx->getBelLocation(bel);
|
||||
if (loc.z == BelZ::CLKDIV_0_Z || loc.z == BelZ::CLKDIV_2_Z)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cluster
|
||||
bool GowinImpl::getClusterPlacement(ClusterId cluster, BelId root_bel,
|
||||
|
@ -62,6 +62,17 @@ inline bool type_is_dsp(IdString cell_type)
|
||||
}
|
||||
inline bool is_dsp(const CellInfo *cell) { return type_is_dsp(cell->type); }
|
||||
|
||||
// Return true if a cell is CLKDIV
|
||||
inline bool type_is_clkdiv(IdString cell_type) { return cell_type == id_CLKDIV; }
|
||||
inline bool is_clkdiv(const CellInfo *cell) { return type_is_clkdiv(cell->type); }
|
||||
|
||||
// Return true if a cell is CLKDIV2
|
||||
inline bool type_is_clkdiv2(IdString cell_type) { return cell_type == id_CLKDIV2; }
|
||||
inline bool is_clkdiv2(const CellInfo *cell) { return type_is_clkdiv2(cell->type); }
|
||||
|
||||
// Return true for HCLK Cells
|
||||
inline bool is_hclk(const CellInfo *cell) { return type_is_clkdiv2(cell->type) || type_is_clkdiv(cell->type); }
|
||||
|
||||
// ==========================================
|
||||
// extra data in the chip db
|
||||
// ==========================================
|
||||
@ -191,7 +202,18 @@ enum
|
||||
ALU54D_1_Z = 556 + 3,
|
||||
MULTALU18X18_1_Z = 560,
|
||||
MULTALU36X18_1_Z = 560 + 1,
|
||||
MULTADDALU18X18_1_Z = 560 + 2
|
||||
MULTADDALU18X18_1_Z = 560 + 2,
|
||||
|
||||
// HCLK Bels
|
||||
CLKDIV2_0_Z = 610,
|
||||
CLKDIV2_1_Z = 611,
|
||||
CLKDIV2_2_Z = 612,
|
||||
CLKDIV2_3_Z = 613,
|
||||
|
||||
CLKDIV_0_Z = 620,
|
||||
CLKDIV_1_Z = 621,
|
||||
CLKDIV_2_Z = 622,
|
||||
CLKDIV_3_Z = 623
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -96,6 +96,17 @@ MULTALU18X18_1_Z = 560
|
||||
MULTALU36X18_1_Z = 560 + 1
|
||||
MULTADDALU18X18_1_Z = 560 + 2
|
||||
|
||||
CLKDIV2_0_Z = 610
|
||||
CLKDIV2_1_Z = 611
|
||||
CLKDIV2_2_Z = 612
|
||||
CLKDIV2_3_Z = 613
|
||||
|
||||
|
||||
CLKDIV_0_Z = 620
|
||||
CLKDIV_1_Z = 621
|
||||
CLKDIV_2_Z = 622
|
||||
CLKDIV_3_Z = 623
|
||||
|
||||
# =======================================
|
||||
# Chipdb additional info
|
||||
# =======================================
|
||||
@ -357,6 +368,47 @@ def create_hclk_switch_matrix(tt: TileType, db: chipdb, x: int, y: int):
|
||||
if not tt.has_wire(src):
|
||||
tt.create_wire(src, "HCLK")
|
||||
tt.create_pip(src, dst)
|
||||
|
||||
hclk_bel_zs = {
|
||||
"CLKDIV2_HCLK0_SECT0": CLKDIV2_0_Z,
|
||||
"CLKDIV2_HCLK0_SECT1": CLKDIV2_1_Z,
|
||||
"CLKDIV2_HCLK1_SECT0": CLKDIV2_2_Z,
|
||||
"CLKDIV2_HCLK1_SECT1": CLKDIV2_3_Z,
|
||||
"CLKDIV_HCLK0_SECT0": CLKDIV_0_Z,
|
||||
"CLKDIV_HCLK0_SECT1": CLKDIV_1_Z,
|
||||
"CLKDIV_HCLK1_SECT0": CLKDIV_2_Z,
|
||||
"CLKDIV_HCLK1_SECT1": CLKDIV_3_Z
|
||||
}
|
||||
|
||||
for bel_name, bel_props in db.grid[y][x].bels.items():
|
||||
if (bel_name not in hclk_bel_zs):
|
||||
continue
|
||||
this_portmap = bel_props.portmap
|
||||
|
||||
if bel_name.startswith("CLKDIV2_"):
|
||||
bel_type = "CLKDIV2"
|
||||
elif bel_name.startswith("CLKDIV_"):
|
||||
bel_type = "CLKDIV"
|
||||
this_bel = tt.create_bel(bel_name, bel_type, hclk_bel_zs[bel_name])
|
||||
|
||||
if (bel_name in ["CLKDIV_HCLK0_SECT1", "CLKDIV_HCLK1_SECT1"]):
|
||||
this_bel.flags |= BEL_FLAG_HIDDEN
|
||||
if bel_type=="CLKDIV":
|
||||
this_bel.flags |= BEL_FLAG_GLOBAL
|
||||
|
||||
known_pins = ["HCLKIN", "RESETN", "CLKOUT"]
|
||||
if bel_type == "CLKDIV":
|
||||
known_pins.append("CALIB")
|
||||
|
||||
for pin in this_portmap.keys():
|
||||
assert pin in known_pins, f"Unknown pin {pin} for bel {this_bel}"
|
||||
if pin in ["CALIB", "RESETN", "HCLKIN"]:
|
||||
pin_direction = PinType.INPUT
|
||||
elif pin in ["CLKOUT"]:
|
||||
pin_direction = PinType.OUTPUT
|
||||
wire_type = "HCLK_CTRL" if pin in ("CALIB", "RESETN") else "HCLK"
|
||||
add_port_wire(tt, this_bel, this_portmap, pin, wire_type, pin_direction)
|
||||
|
||||
|
||||
# map spine -> dqce bel
|
||||
dqce_bels = {}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "himbaechel_constids.h"
|
||||
#include "himbaechel_helpers.h"
|
||||
|
||||
#include <queue>
|
||||
#include "gowin.h"
|
||||
#include "gowin_utils.h"
|
||||
|
||||
@ -290,4 +291,145 @@ CellInfo *GowinUtils::dsp_bus_dst(const CellInfo *ci, const char *bus_prefix, in
|
||||
return connected_to_cell;
|
||||
}
|
||||
|
||||
// Use the upper CLKDIV as the id for a hclk section
|
||||
IdStringList GowinUtils::get_hclk_id(BelId hclk_bel) const
|
||||
{
|
||||
IdString bel_type = ctx->getBelType(hclk_bel);
|
||||
NPNR_ASSERT(hclk_bel != BelId() && bel_type.in(id_CLKDIV2, id_CLKDIV));
|
||||
Loc id_loc = Loc(ctx->getBelLocation(hclk_bel));
|
||||
if (bel_type == id_CLKDIV) {
|
||||
return get_hclk_id(get_clkdiv2_for_clkdiv(hclk_bel));
|
||||
} else if (id_loc.z == BelZ::CLKDIV2_0_Z || id_loc.z == BelZ::CLKDIV2_2_Z)
|
||||
return ctx->getBelName(hclk_bel);
|
||||
else
|
||||
return ctx->getBelName(ctx->getBelByLocation(Loc(id_loc.x, id_loc.y, id_loc.z - 1)));
|
||||
return IdStringList();
|
||||
}
|
||||
|
||||
// Get the clkdiv in the same section as a clkdiv2
|
||||
BelId GowinUtils::get_clkdiv_for_clkdiv2(BelId clkdiv2_bel) const
|
||||
{
|
||||
NPNR_ASSERT(clkdiv2_bel != BelId() && (ctx->getBelType(clkdiv2_bel) == id_CLKDIV2));
|
||||
Loc clkdiv_loc = ctx->getBelLocation(clkdiv2_bel);
|
||||
clkdiv_loc.z = BelZ::CLKDIV_0_Z + (clkdiv_loc.z - BelZ::CLKDIV2_0_Z);
|
||||
return ctx->getBelByLocation(clkdiv_loc);
|
||||
}
|
||||
|
||||
// Get the clkdiv2 in the same section as a clkdiv
|
||||
BelId GowinUtils::get_clkdiv2_for_clkdiv(BelId clkdiv_bel) const
|
||||
{
|
||||
NPNR_ASSERT(clkdiv_bel != BelId() && (ctx->getBelType(clkdiv_bel) == id_CLKDIV));
|
||||
Loc clkdiv_loc = ctx->getBelLocation(clkdiv_bel);
|
||||
Loc clkdiv2_loc = Loc(clkdiv_loc.x, clkdiv_loc.y, BelZ::CLKDIV2_0_Z + (clkdiv_loc.z - BelZ::CLKDIV_0_Z));
|
||||
return ctx->getBelByLocation(clkdiv2_loc);
|
||||
}
|
||||
|
||||
// Get the clkdiv in the neighbouring section to a clkdiv
|
||||
BelId GowinUtils::get_other_hclk_clkdiv(BelId clkdiv_bel) const
|
||||
{
|
||||
NPNR_ASSERT(clkdiv_bel != BelId() && (ctx->getBelType(clkdiv_bel) == id_CLKDIV));
|
||||
Loc other_loc = Loc(ctx->getBelLocation(clkdiv_bel));
|
||||
int dz = BelZ::CLKDIV_1_Z - BelZ::CLKDIV_0_Z;
|
||||
if (other_loc.z == BelZ::CLKDIV_1_Z || other_loc.z == BelZ::CLKDIV_3_Z)
|
||||
dz = -dz;
|
||||
other_loc.z += dz;
|
||||
return ctx->getBelByLocation(other_loc);
|
||||
}
|
||||
|
||||
// Get the clkdiv2 in the neighbouring section to a clkdiv2
|
||||
BelId GowinUtils::get_other_hclk_clkdiv2(BelId clkdiv2_bel) const
|
||||
{
|
||||
NPNR_ASSERT(clkdiv2_bel != BelId() && (ctx->getBelType(clkdiv2_bel) == id_CLKDIV2));
|
||||
Loc other_loc = Loc(ctx->getBelLocation(clkdiv2_bel));
|
||||
int dz = BelZ::CLKDIV2_1_Z - BelZ::CLKDIV2_0_Z;
|
||||
if (other_loc.z == BelZ::CLKDIV2_1_Z || other_loc.z == BelZ::CLKDIV2_3_Z)
|
||||
dz = -dz;
|
||||
other_loc.z += dz;
|
||||
return ctx->getBelByLocation(other_loc);
|
||||
}
|
||||
|
||||
// Credit: https://cp-algorithms.com/graph/kuhn_maximum_bipartite_matching.html
|
||||
std::vector<int> GowinUtils::kuhn_find_maximum_bipartite_matching(int n, int k, std::vector<std::vector<int>> &g)
|
||||
{
|
||||
std::vector<int> mt;
|
||||
std::vector<bool> used(n);
|
||||
|
||||
auto try_kuhn = [&](int v, auto &try_kuhn_ref) -> bool {
|
||||
if (used[v])
|
||||
return false;
|
||||
used[v] = true;
|
||||
for (int to : g[v]) {
|
||||
if (mt[to] == -1 || try_kuhn_ref(mt[to], try_kuhn_ref)) {
|
||||
mt[to] = v;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
mt.assign(k, -1);
|
||||
for (int v = 0; v < n; ++v) {
|
||||
used.assign(n, false);
|
||||
try_kuhn(v, try_kuhn);
|
||||
}
|
||||
|
||||
return mt;
|
||||
}
|
||||
|
||||
// original implementation: nextpnr/machxo2/pack.cc
|
||||
// Using a BFS, search for bels of a given type either upstream or downstream of another cell
|
||||
void GowinUtils::find_connected_bels(const CellInfo *cell, IdString port, IdString dest_type, IdString dest_pin,
|
||||
int iter_limit, std::vector<BelId> &candidates)
|
||||
{
|
||||
int iter = 0;
|
||||
std::queue<WireId> visit;
|
||||
pool<WireId> seen_wires;
|
||||
pool<BelId> seen_bels;
|
||||
|
||||
BelId bel = cell->bel;
|
||||
if (bel == BelId())
|
||||
return;
|
||||
WireId start_wire = ctx->getBelPinWire(bel, port);
|
||||
NPNR_ASSERT(start_wire != WireId());
|
||||
PortType dir = ctx->getBelPinType(bel, port);
|
||||
|
||||
visit.push(start_wire);
|
||||
|
||||
while (!visit.empty() && (iter++ < iter_limit)) {
|
||||
WireId cursor = visit.front();
|
||||
visit.pop();
|
||||
// Check to see if we have reached a valid bel pin
|
||||
for (auto bp : ctx->getWireBelPins(cursor)) {
|
||||
if (ctx->getBelType(bp.bel) != dest_type)
|
||||
continue;
|
||||
if (dest_pin != IdString() && bp.pin != dest_pin)
|
||||
continue;
|
||||
if (seen_bels.count(bp.bel))
|
||||
continue;
|
||||
seen_bels.insert(bp.bel);
|
||||
candidates.push_back(bp.bel);
|
||||
}
|
||||
// Search in the appropriate direction up/downstream of the cursor
|
||||
if (dir == PORT_OUT) {
|
||||
for (PipId p : ctx->getPipsDownhill(cursor))
|
||||
if (ctx->checkPipAvail(p)) {
|
||||
WireId dst = ctx->getPipDstWire(p);
|
||||
if (seen_wires.count(dst))
|
||||
continue;
|
||||
seen_wires.insert(dst);
|
||||
visit.push(dst);
|
||||
}
|
||||
} else {
|
||||
for (PipId p : ctx->getPipsUphill(cursor))
|
||||
if (ctx->checkPipAvail(p)) {
|
||||
WireId src = ctx->getPipSrcWire(p);
|
||||
if (seen_wires.count(src))
|
||||
continue;
|
||||
seen_wires.insert(src);
|
||||
visit.push(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
@ -94,6 +94,59 @@ struct GowinUtils
|
||||
|
||||
// make cell but do not include it in the list of chip cells.
|
||||
std::unique_ptr<CellInfo> create_cell(IdString name, IdString type);
|
||||
|
||||
// HCLK
|
||||
BelId get_clkdiv_for_clkdiv2(BelId clkdiv2_bel) const;
|
||||
BelId get_other_hclk_clkdiv2(BelId clkdiv2_bel) const;
|
||||
BelId get_other_hclk_clkdiv(BelId clkdiv_bel) const;
|
||||
BelId get_clkdiv2_for_clkdiv(BelId clkdiv_bel) const;
|
||||
IdStringList get_hclk_id(BelId hclk_bel) const; // use the upper CLKDIV2 (CLKDIV2_0 orCLKDIV2_2) as an id
|
||||
|
||||
// Find Bels connected to a bound cell
|
||||
void find_connected_bels(const CellInfo *cell, IdString port, IdString dest_type, IdString dest_pin, int iter_limit,
|
||||
std::vector<BelId> &candidates);
|
||||
|
||||
// Find a maximum bipartite matching
|
||||
template <typename T1, typename T2> std::map<T1, T2> find_maximum_bipartite_matching(std::map<T1, std::set<T2>> &G)
|
||||
{
|
||||
std::map<int, T1> U;
|
||||
std::map<int, T2> V;
|
||||
std::map<T2, int> V_IDX;
|
||||
std::vector<std::vector<int>> int_graph(G.size());
|
||||
|
||||
int u_idx = 0;
|
||||
int v_idx = 0;
|
||||
|
||||
// Translate the input graph to an integer graph
|
||||
for (auto row : G) {
|
||||
U.insert(std::pair(u_idx, row.first));
|
||||
for (auto v : row.second) {
|
||||
if (V_IDX.find(v) == V_IDX.end()) {
|
||||
V_IDX[v] = v_idx;
|
||||
V[v_idx] = v;
|
||||
v_idx++;
|
||||
}
|
||||
int_graph[u_idx].push_back(V_IDX[v]);
|
||||
}
|
||||
u_idx++;
|
||||
}
|
||||
|
||||
std::vector<int> int_matching = kuhn_find_maximum_bipartite_matching(u_idx, v_idx, int_graph);
|
||||
std::map<T1, T2> ret_matching;
|
||||
|
||||
int m_idx = 0;
|
||||
for (auto val : int_matching) {
|
||||
if (val >= 0) { // elements that are not matched have a value of -1
|
||||
ret_matching[U[val]] = V[m_idx];
|
||||
}
|
||||
m_idx++;
|
||||
}
|
||||
|
||||
return ret_matching;
|
||||
}
|
||||
|
||||
// Find a maximum matching in a bipartite graph, g
|
||||
std::vector<int> kuhn_find_maximum_bipartite_matching(int n, int k, std::vector<std::vector<int>> &g);
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
@ -1,7 +1,6 @@
|
||||
#include "design_utils.h"
|
||||
#include "log.h"
|
||||
#include "nextpnr.h"
|
||||
#include "util.h"
|
||||
|
||||
#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
|
||||
#include "himbaechel_constids.h"
|
||||
@ -1325,7 +1324,7 @@ struct GowinPacker
|
||||
{
|
||||
int num = (bit_width == 9 || bit_width == 18 || bit_width == 36) ? 36 : 32;
|
||||
for (int i = 0, j = offset; i < num; ++i, ++j) {
|
||||
if (((j + 1) % 9) == 0 && (bit_width == 16 || bit_width == 32)) {
|
||||
if (((i + 1) % 9) == 0 && (bit_width == 16 || bit_width == 32)) {
|
||||
++j;
|
||||
}
|
||||
ci->renamePort(ctx->idf(from, i), ctx->idf(to, offset ? j % 36 : j));
|
||||
@ -1391,15 +1390,6 @@ struct GowinPacker
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
switch (idx) {
|
||||
case 1:
|
||||
init |= init << 2; /* fallthrough */
|
||||
case 2:
|
||||
init |= init << 4;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
lut->setParam(id_INIT, init);
|
||||
|
||||
new_cells.push_back(std::move(lut_cell));
|
||||
@ -1416,7 +1406,7 @@ struct GowinPacker
|
||||
if (bit_width == 32 || bit_width == 36) {
|
||||
return;
|
||||
}
|
||||
int read_mode = int_or_default(ci->params, id_READ_MODE, 0);
|
||||
int read_mode = ci->params.at(id_READ_MODE).as_int64();
|
||||
if (read_mode == 0) {
|
||||
return;
|
||||
}
|
||||
@ -1521,7 +1511,7 @@ struct GowinPacker
|
||||
CellInfo *new_ce_net_src = ce_pre_dff;
|
||||
|
||||
// add delay register in pipeline mode
|
||||
int read_mode = int_or_default(ci->params, id_READ_MODE, 0);
|
||||
int read_mode = ci->params.at(id_READ_MODE).as_int64();
|
||||
if (read_mode) {
|
||||
auto ce_pipe_dff_cell = gwu.create_cell(create_aux_name(ci->name, 0, "_ce_pipe_dff$"), id_DFF);
|
||||
new_cells.push_back(std::move(ce_pipe_dff_cell));
|
||||
@ -1597,7 +1587,10 @@ struct GowinPacker
|
||||
ci->connectPort(port, vcc_net);
|
||||
}
|
||||
|
||||
ci->addInput(id_WRE);
|
||||
ci->connectPort(id_WRE, vss_net);
|
||||
ci->addInput(id_WREB);
|
||||
ci->connectPort(id_WREB, vss_net);
|
||||
|
||||
if (!ci->params.count(id_BIT_WIDTH)) {
|
||||
ci->setParam(id_BIT_WIDTH, Property(default_bw, 32));
|
||||
@ -1609,19 +1602,10 @@ struct GowinPacker
|
||||
ci->copyPortTo(id_CE, ci, id_CEB);
|
||||
ci->copyPortTo(id_OCE, ci, id_OCEB);
|
||||
ci->copyPortTo(id_RESET, ci, id_RESETB);
|
||||
ci->connectPort(id_WREB, vcc_net);
|
||||
|
||||
// disconnect lower address bits for ROM
|
||||
std::array rom_ignore_bits = {2, 4, 8, 16, 32};
|
||||
std::array romx9_ignore_bits = {9, 9, 9, 18, 36};
|
||||
for (unsigned int i = 0; i < 14; ++i) {
|
||||
if (i < size(rom_ignore_bits) && ((ci->type == id_pROM && bit_width >= rom_ignore_bits[i]) ||
|
||||
(ci->type == id_pROMX9 && bit_width >= romx9_ignore_bits[i]))) {
|
||||
ci->disconnectPort(ctx->idf("AD[%d]", i));
|
||||
} else {
|
||||
ci->renamePort(ctx->idf("AD[%d]", i), ctx->idf("ADA%d", i));
|
||||
ci->copyPortTo(ctx->idf("ADA%d", i), ci, ctx->idf("ADB%d", i));
|
||||
}
|
||||
for (int i = 0; i < 14; ++i) {
|
||||
ci->renamePort(ctx->idf("AD[%d]", i), ctx->idf("ADA%d", i));
|
||||
ci->copyPortTo(ctx->idf("ADA%d", i), ci, ctx->idf("ADB%d", i));
|
||||
}
|
||||
bsram_rename_ports(ci, bit_width, "DO[%d]", "DO%d");
|
||||
} else {
|
||||
@ -1630,18 +1614,9 @@ struct GowinPacker
|
||||
ci->renamePort(id_OCE, id_OCEB);
|
||||
ci->renamePort(id_CE, id_CEB);
|
||||
ci->renamePort(id_RESET, id_RESETB);
|
||||
ci->connectPort(id_WREB, vss_net);
|
||||
|
||||
ci->addInput(id_CE);
|
||||
ci->connectPort(id_CE, vcc_net);
|
||||
ci->disconnectPort(id_OCEB);
|
||||
|
||||
int read_mode = int_or_default(ci->params, id_READ_MODE, 0);
|
||||
if (read_mode) {
|
||||
ci->connectPort(id_OCEB, vcc_net);
|
||||
} else {
|
||||
ci->copyPortTo(id_CEB, ci, id_OCEB);
|
||||
}
|
||||
ci->addInput(id_CEA);
|
||||
ci->connectPort(id_CEA, vss_net);
|
||||
for (int i = 0; i < 14; ++i) {
|
||||
ci->renamePort(ctx->idf("AD[%d]", i), ctx->idf("ADB%d", i));
|
||||
}
|
||||
@ -1823,21 +1798,6 @@ struct GowinPacker
|
||||
bsram_fix_blksel(ci, new_cells);
|
||||
}
|
||||
|
||||
// The statement in the Gowin documentation that in the reading mode
|
||||
// "READ_MODE=0" the output register is not used and the OCE signal is
|
||||
// ignored is not confirmed by practice - if the OCE was left
|
||||
// unconnected or connected to the constant network, then a change in
|
||||
// output data was observed even with CE=0, as well as the absence of
|
||||
// such at CE=1.
|
||||
// Synchronizing CE and OCE helps but it's definitely a hack.
|
||||
NetInfo *oce_net = ci->getPort(id_OCE);
|
||||
if (oce_net == nullptr || oce_net->name == ctx->id("$PACKER_VCC") || oce_net->name == ctx->id("$PACKER_GND")) {
|
||||
if (oce_net != nullptr) {
|
||||
ci->disconnectPort(id_OCE);
|
||||
}
|
||||
ci->copyPortTo(id_CE, ci, id_OCE);
|
||||
}
|
||||
|
||||
// XXX UG285-1.3.6_E Gowin BSRAM & SSRAM User Guide:
|
||||
// For GW1N-9/GW1NR-9/GW1NS-4 series, 32/36-bit SP/SPX9 is divided into two
|
||||
// SP/SPX9s, which occupy two BSRAMs.
|
||||
@ -2981,6 +2941,39 @@ struct GowinPacker
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// HCLK -- CLKDIV and CLKDIV2 for now
|
||||
// ===================================
|
||||
void pack_hclk(void)
|
||||
{
|
||||
log_info("Pack HCLK cells...\n");
|
||||
|
||||
for (auto &cell : ctx->cells) {
|
||||
auto ci = cell.second.get();
|
||||
if (ci->type != id_CLKDIV)
|
||||
continue;
|
||||
NetInfo *hclk_in = ci->getPort(id_HCLKIN);
|
||||
if (hclk_in) {
|
||||
CellInfo *this_driver = hclk_in->driver.cell;
|
||||
if (this_driver && this_driver->type == id_CLKDIV2) {
|
||||
NetInfo *out = this_driver->getPort(id_CLKOUT);
|
||||
if (out->users.entries() > 1) {
|
||||
// We could do as the IDE does sometimes and replicate the CLKDIV2 cell
|
||||
// as many times as we need. For now, we keep things simple
|
||||
log_error("CLKDIV2 that drives CLKDIV should drive no other cells\n");
|
||||
}
|
||||
ci->cluster = ci->name;
|
||||
this_driver->cluster = ci->name;
|
||||
ci->constr_children.push_back(this_driver);
|
||||
this_driver->constr_x = 0;
|
||||
this_driver->constr_y = 0;
|
||||
this_driver->constr_z = BelZ::CLKDIV2_0_Z - BelZ::CLKDIV_0_Z;
|
||||
this_driver->constr_abs_z = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// Create entry points to the clock system
|
||||
// =========================================
|
||||
@ -3097,6 +3090,9 @@ struct GowinPacker
|
||||
pack_gsr();
|
||||
ctx->check();
|
||||
|
||||
pack_hclk();
|
||||
ctx->check();
|
||||
|
||||
pack_bandgap();
|
||||
ctx->check();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user