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:
Saviour Owolabi 2024-08-03 14:57:22 +01:00 committed by GitHub
parent 11d335c7ce
commit e9e7dce23d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 695 additions and 57 deletions

View File

@ -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)

View File

@ -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)) {

View File

@ -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;
}

View File

@ -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,

View File

@ -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
};
}

View File

@ -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 = {}

View File

@ -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

View File

@ -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

View File

@ -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();