nextpnr/himbaechel/uarch/gowin/gowin.cc
YRabbit 10a5a44b81 Gowin. Implement clock management primitives.
DQCE and DCS primitives are added.

DQCE allows the internal logic to enable or disable the clock network in
the quadrant. When clock network is disabled, all logic drivern by this
clock is no longer toggled, thus reducing the total power consumtion of
the device.

DCS allows you to select one of four sources for two clock wires (6 and 7).
Wires 6 and 7 have not been used up to this point.

Since "hardware" primitives operate strictly in their own quadrants,
user-specified primitives are converted into one or more "hardware"
primitives as needed.

Also:
  - minor edits to make the most of helper functions like connectPorts()
  - when creating bases, the corresponding constants are assigned to the
    VCC and GND wires, but for now huge nodes are used because, for an
    unknown reason, the constants mechanism makes large examples
    inoperable. So for now we remain on the nodes.

Compatible with older Apicula databases.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
2024-08-02 14:12:16 +02:00

708 lines
25 KiB
C++

#include <regex>
#include "himbaechel_api.h"
#include "himbaechel_helpers.h"
#include "log.h"
#include "nextpnr.h"
#define GEN_INIT_CONSTIDS
#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
#include "himbaechel_constids.h"
#include "cst.h"
#include "globals.h"
#include "gowin.h"
#include "gowin_utils.h"
#include "pack.h"
NEXTPNR_NAMESPACE_BEGIN
namespace {
struct GowinImpl : HimbaechelAPI
{
~GowinImpl(){};
void init_database(Arch *arch) override;
void init(Context *ctx) override;
void pack() override;
void prePlace() override;
void postPlace() override;
void preRoute() override;
void postRoute() override;
bool isBelLocationValid(BelId bel, bool explain_invalid) const override;
void notifyBelChange(BelId bel, CellInfo *cell) override;
// Bel bucket functions
IdString getBelBucketForCellType(IdString cell_type) const override;
bool isValidBelForCellType(IdString cell_type, BelId bel) const override;
// wires
bool checkPipAvail(PipId pip) const override;
// Cluster
bool isClusterStrict(const CellInfo *cell) const { return true; }
bool getClusterPlacement(ClusterId cluster, BelId root_bel,
std::vector<std::pair<CellInfo *, BelId>> &placement) const;
private:
HimbaechelHelpers h;
GowinUtils gwu;
IdString chip;
IdString partno;
std::set<BelId> inactive_bels;
// Validity checking
struct GowinCellInfo
{
// slice info
const NetInfo *lut_f = nullptr;
const NetInfo *ff_d = nullptr, *ff_ce = nullptr, *ff_clk = nullptr, *ff_lsr = nullptr;
const NetInfo *alu_sum = nullptr;
// dsp info
const NetInfo *dsp_asign = nullptr, *dsp_bsign = nullptr, *dsp_asel = nullptr, *dsp_bsel = nullptr,
*dsp_ce = nullptr, *dsp_clk = nullptr, *dsp_reset = nullptr;
bool dsp_soa_reg;
};
std::vector<GowinCellInfo> fast_cell_info;
void assign_cell_info();
// 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
// primitives use the same signals for CE, CLK and especially RESET, so
// these wires are few and need to be controlled.
struct dsp_net_counters
{
dict<IdString, int> ce;
dict<IdString, int> clk;
dict<IdString, int> reset;
};
dict<BelId, dsp_net_counters> dsp_net_cnt;
dict<BelId, CellInfo *> dsp_bel2cell; // Remember the connection with cells
// since this information is already lost during unbinding
void adjust_dsp_pin_mapping(void);
// 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;
};
struct GowinArch : HimbaechelArch
{
GowinArch() : HimbaechelArch("gowin"){};
bool match_device(const std::string &device) override { return device.size() > 2 && device.substr(0, 2) == "GW"; }
std::unique_ptr<HimbaechelAPI> create(const std::string &device, const dict<std::string, std::string> &args)
{
return std::make_unique<GowinImpl>();
}
} gowinrArch;
void GowinImpl::init_database(Arch *arch)
{
init_uarch_constids(arch);
const ArchArgs &args = arch->args;
std::string family;
if (args.options.count("family")) {
family = args.options.at("family");
} else {
bool GW2 = args.device.rfind("GW2A", 0) == 0;
if (GW2) {
log_error("For the GW2A series you need to specify --vopt family=GW2A-18 or --vopt family=GW2A-18C\n");
} else {
std::regex devicere = std::regex("GW1N([SZ]?)[A-Z]*-(LV|UV|UX)([0-9])(C?).*");
std::smatch match;
if (!std::regex_match(args.device, match, devicere)) {
log_error("Invalid device %s\n", args.device.c_str());
}
family = stringf("GW1N%s-%s", match[1].str().c_str(), match[3].str().c_str());
if (family.rfind("GW1N-9", 0) == 0) {
log_error("For the GW1N-9 series you need to specify --vopt family=GW1N-9 or --vopt family=GW1N-9C\n");
}
}
}
arch->load_chipdb(stringf("gowin/chipdb-%s.bin", family.c_str()));
// These fields go in the header of the output JSON file and can help
// gowin_pack support different architectures
arch->settings[arch->id("packer.arch")] = std::string("himbaechel/gowin");
arch->settings[arch->id("packer.chipdb")] = family;
chip = arch->id(family);
std::string pn = args.device;
partno = arch->id(pn);
arch->settings[arch->id("packer.partno")] = pn;
}
void GowinImpl::init(Context *ctx)
{
h.init(ctx);
HimbaechelAPI::init(ctx);
gwu.init(ctx);
const ArchArgs &args = ctx->getArchArgs();
// package and speed class
std::regex speedre = std::regex("(.*)(C[0-9]/I[0-9])$");
std::smatch match;
IdString spd;
IdString package_idx;
std::string pn = args.device;
if (std::regex_match(pn, match, speedre)) {
package_idx = ctx->id(match[1]);
spd = ctx->id(match[2]);
} else {
if (pn.length() > 2 && pn.compare(pn.length() - 2, 2, "ES")) {
package_idx = ctx->id(pn.substr(pn.length() - 2));
spd = ctx->id("ES");
}
}
// log_info("packages:%ld\n", ctx->chip_info->packages.ssize());
for (int i = 0; i < ctx->chip_info->packages.ssize(); ++i) {
if (IdString(ctx->chip_info->packages[i].name) == package_idx) {
// log_info("i:%d %s\n", i, package_idx.c_str(ctx));
ctx->package_info = &ctx->chip_info->packages[i];
break;
}
}
if (ctx->package_info == nullptr) {
log_error("No package for partnumber %s\n", partno.c_str(ctx));
}
// constraints
if (args.options.count("cst")) {
ctx->settings[ctx->id("cst.filename")] = args.options.at("cst");
}
}
// We do not allow the use of global wires that bypass a special router.
bool GowinImpl::checkPipAvail(PipId pip) const
{
return (ctx->getWireConstantValue(ctx->getPipSrcWire(pip)) != IdString()) || (!gwu.is_global_pip(pip));
}
void GowinImpl::pack()
{
if (ctx->settings.count(ctx->id("cst.filename"))) {
std::string filename = ctx->settings[ctx->id("cst.filename")].as_string();
std::ifstream in(filename);
if (!in) {
log_error("failed to open CST file '%s'\n", filename.c_str());
}
if (!gowin_apply_constraints(ctx, in)) {
log_error("failed to parse CST file '%s'\n", filename.c_str());
}
}
gowin_pack(ctx);
}
// One DSP macro, in a rough approximation, consists of 5 large operating
// blocks (pre-adders, multipliers and alu), at almost every input (blocks
// usually have two of them) you can turn on registers, in addition, there are
// registers on a dedicated operand shift line between DSP and registers at
// the outputs. As we see, the number of registers is large, but the DSP has
// only four inputs for each of the CE, CLK and RESET signals, and here we tell
// gowin_pack which version of each signal is used by which block.
// We also indicate to the router which Bel's pin to use.
void GowinImpl::adjust_dsp_pin_mapping(void)
{
for (auto b2c : dsp_bel2cell) {
BelId bel = b2c.first;
Loc loc = ctx->getBelLocation(bel);
CellInfo *ci = b2c.second;
const auto dsp_data = fast_cell_info.at(ci->flat_index);
auto set_cell_bel_pin = [&](dict<IdString, int> nets, IdString pin, IdString net_name, const char *fmt,
const char *fmt_double = nullptr) {
int i = 0;
for (auto net_cnt : nets) {
if (net_cnt.first == net_name) {
break;
}
++i;
}
ci->cell_bel_pins.at(pin).clear();
if (fmt_double == nullptr) {
ci->cell_bel_pins.at(pin).push_back(ctx->idf(fmt, i));
} else {
ci->cell_bel_pins.at(pin).push_back(ctx->idf(fmt_double, i, 0));
ci->cell_bel_pins.at(pin).push_back(ctx->idf(fmt_double, i, 1));
}
ci->setAttr(pin, i);
};
if (dsp_data.dsp_reset != nullptr) {
BelId dsp = ctx->getBelByLocation(Loc(loc.x, loc.y, BelZ::DSP_Z));
set_cell_bel_pin(dsp_net_cnt.at(dsp).reset, id_RESET, dsp_data.dsp_reset->name, "RESET%d",
ci->type == id_MULT36X36 ? "RESET%d%d" : nullptr);
}
if (dsp_data.dsp_ce != nullptr) {
BelId dsp = ctx->getBelByLocation(Loc(loc.x, loc.y, gwu.get_dsp_macro(loc.z)));
set_cell_bel_pin(dsp_net_cnt.at(dsp).ce, id_CE, dsp_data.dsp_ce->name, "CE%d",
ci->type == id_MULT36X36 ? "CE%d%d" : nullptr);
}
if (dsp_data.dsp_clk != nullptr) {
BelId dsp = ctx->getBelByLocation(Loc(loc.x, loc.y, gwu.get_dsp_macro(loc.z)));
set_cell_bel_pin(dsp_net_cnt.at(dsp).clk, id_CLK, dsp_data.dsp_clk->name, "CLK%d",
ci->type == id_MULT36X36 ? "CLK%d%d" : nullptr);
}
}
}
void GowinImpl::prePlace() { assign_cell_info(); }
void GowinImpl::postPlace()
{
gwu.has_SP32();
if (ctx->debug) {
log_info("================== Final Placement ===================\n");
for (auto &cell : ctx->cells) {
auto ci = cell.second.get();
if (ci->bel != BelId()) {
log_info("%s: %s\n", ctx->nameOfBel(ci->bel), ctx->nameOf(ci));
} else {
log_info("unknown: %s\n", ctx->nameOf(ci));
}
}
log_break();
}
// adjust cell pin to bel pin mapping for DSP cells (CE, CLK and RESET pins)
adjust_dsp_pin_mapping();
}
void GowinImpl::preRoute() { gowin_route_globals(ctx); }
void GowinImpl::postRoute()
{
std::set<IdString> visited_hclk_users;
for (auto &cell : ctx->cells) {
auto ci = cell.second.get();
if (ci->type.in(id_IOLOGICI, id_IOLOGICO, id_IOLOGIC) ||
((is_iologici(ci) || is_iologico(ci)) && !ci->type.in(id_ODDR, id_ODDRC, id_IDDR, id_IDDRC))) {
if (visited_hclk_users.find(ci->name) == visited_hclk_users.end()) {
// mark FCLK<-HCLK connections
const NetInfo *h_net = ci->getPort(id_FCLK);
if (h_net) {
for (auto &user : h_net->users) {
if (user.port != id_FCLK) {
continue;
}
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)) {
user.cell->setAttr(id_IOLOGIC_FCLK, Property(up_wire_name.str(ctx)));
if (ctx->debug) {
log_info("set IOLOGIC_FCLK to %s\n", up_wire_name.c_str(ctx));
}
}
if (ctx->debug) {
log_info("HCLK user cell:%s, port:%s, wire:%s, pip:%s, up wire:%s\n",
ctx->nameOf(user.cell), user.port.c_str(ctx),
ctx->nameOfWire(ctx->getNetinfoSinkWire(h_net, user, 0)), ctx->nameOfPip(up_pip),
ctx->nameOfWire(ctx->getPipSrcWire(up_pip)));
}
}
}
}
}
}
}
bool GowinImpl::isBelLocationValid(BelId bel, bool explain_invalid) const
{
Loc l = ctx->getBelLocation(bel);
IdString bel_type = ctx->getBelType(bel);
if (!ctx->getBoundBelCell(bel)) {
return true;
}
switch (bel_type.hash()) {
case ID_LUT4: /* fall-through */
case ID_DFF:
return slice_valid(l.x, l.y, l.z / 2);
case ID_ALU:
return slice_valid(l.x, l.y, l.z - BelZ::ALU0_Z);
case ID_RAM16SDP4:
// only slices 4 and 5 are critical for RAM
return slice_valid(l.x, l.y, l.z - BelZ::RAMW_Z + 5) && slice_valid(l.x, l.y, l.z - BelZ::RAMW_Z + 4);
case ID_PADD9: /* fall-through */
case ID_PADD18: /* fall-through */
case ID_MULT9X9: /* fall-through */
case ID_MULT18X18: /* fall-through */
case ID_MULTADDALU18X18: /* fall-through */
case ID_MULTALU18X18: /* fall-through */
case ID_MULTALU36X18: /* fall-through */
case ID_MULT36X36: /* fall-through */
case ID_ALU54D:
return dsp_valid(l, bel_type, explain_invalid);
}
return true;
}
// Bel bucket functions
IdString GowinImpl::getBelBucketForCellType(IdString cell_type) const
{
if (cell_type.in(id_IBUF, id_OBUF)) {
return id_IOB;
}
if (type_is_lut(cell_type)) {
return id_LUT4;
}
if (type_is_dff(cell_type)) {
return id_DFF;
}
if (type_is_ssram(cell_type)) {
return id_RAM16SDP4;
}
if (type_is_iologici(cell_type)) {
return id_IOLOGICI;
}
if (type_is_iologico(cell_type)) {
return id_IOLOGICO;
}
if (type_is_bsram(cell_type)) {
return id_BSRAM;
}
if (cell_type == id_GOWIN_GND) {
return id_GND;
}
if (cell_type == id_GOWIN_VCC) {
return id_VCC;
}
return cell_type;
}
bool GowinImpl::isValidBelForCellType(IdString cell_type, BelId bel) const
{
if (cell_type == id_DUMMY_CELL) {
return true;
}
IdString bel_type = ctx->getBelType(bel);
if (bel_type == id_IOB) {
return cell_type.in(id_IBUF, id_OBUF);
}
if (bel_type == id_LUT4) {
return type_is_lut(cell_type);
}
if (bel_type == id_DFF) {
return type_is_dff(cell_type);
}
if (bel_type == id_RAM16SDP4) {
return type_is_ssram(cell_type);
}
if (bel_type == id_IOLOGICI) {
return type_is_iologici(cell_type);
}
if (bel_type == id_IOLOGICO) {
return type_is_iologico(cell_type);
}
if (bel_type == id_BSRAM) {
return type_is_bsram(cell_type);
}
if (bel_type == id_GND) {
return cell_type == id_GOWIN_GND;
}
if (bel_type == id_VCC) {
return cell_type == id_GOWIN_VCC;
}
return (bel_type == cell_type);
}
void GowinImpl::assign_cell_info()
{
fast_cell_info.resize(ctx->cells.size());
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
auto &fc = fast_cell_info.at(ci->flat_index);
if (is_lut(ci)) {
fc.lut_f = ci->getPort(id_F);
continue;
}
if (is_dff(ci)) {
fc.ff_d = ci->getPort(id_D);
fc.ff_clk = ci->getPort(id_CLK);
fc.ff_ce = ci->getPort(id_CE);
for (IdString port : {id_SET, id_RESET, id_PRESET, id_CLEAR}) {
fc.ff_lsr = ci->getPort(port);
if (fc.ff_lsr != nullptr) {
break;
}
}
continue;
}
if (is_alu(ci)) {
fc.alu_sum = ci->getPort(id_SUM);
continue;
}
auto get_net = [&](IdString port_id) {
NetInfo *ni = ci->getPort(port_id);
if (ni != nullptr && ni->driver.cell == nullptr) {
ni = nullptr;
}
return ni;
};
if (is_dsp(ci)) {
fc.dsp_reset = get_net(id_RESET);
fc.dsp_clk = get_net(id_CLK);
fc.dsp_ce = get_net(id_CE);
fc.dsp_asign = get_net(id_ASIGN);
fc.dsp_bsign = get_net(id_BSIGN);
fc.dsp_asel = get_net(id_ASEL);
fc.dsp_bsel = get_net(id_BSEL);
fc.dsp_soa_reg = ci->params.count(id_SOA_REG) && ci->params.at(id_SOA_REG).as_int64() == 1;
}
}
}
// DFFs must be same type or compatible
inline bool incompatible_ffs(const CellInfo *ff, const CellInfo *adj_ff)
{
return ff->type != adj_ff->type &&
((ff->type == id_DFFS && adj_ff->type != id_DFFR) || (ff->type == id_DFFR && adj_ff->type != id_DFFS) ||
(ff->type == id_DFFSE && adj_ff->type != id_DFFRE) || (ff->type == id_DFFRE && adj_ff->type != id_DFFSE) ||
(ff->type == id_DFFP && adj_ff->type != id_DFFC) || (ff->type == id_DFFC && adj_ff->type != id_DFFP) ||
(ff->type == id_DFFPE && adj_ff->type != id_DFFCE) || (ff->type == id_DFFCE && adj_ff->type != id_DFFPE) ||
(ff->type == id_DFFNS && adj_ff->type != id_DFFNR) || (ff->type == id_DFFNR && adj_ff->type != id_DFFNS) ||
(ff->type == id_DFFNSE && adj_ff->type != id_DFFNRE) ||
(ff->type == id_DFFNRE && adj_ff->type != id_DFFNSE) ||
(ff->type == id_DFFNP && adj_ff->type != id_DFFNC) || (ff->type == id_DFFNC && adj_ff->type != id_DFFNP) ||
(ff->type == id_DFFNPE && adj_ff->type != id_DFFNCE) ||
(ff->type == id_DFFNCE && adj_ff->type != id_DFFNPE));
}
// placement validation
bool GowinImpl::dsp_valid(Loc l, IdString bel_type, bool explain_invalid) const
{
const CellInfo *dsp = ctx->getBoundBelCell(ctx->getBelByLocation(l));
const auto &dsp_data = fast_cell_info.at(dsp->flat_index);
// check for shift out register - there is only one for macro
if (dsp_data.dsp_soa_reg) {
if (l.z == BelZ::MULT18X18_0_1_Z || l.z == BelZ::MULT18X18_1_1_Z || l.z == BelZ::MULT9X9_0_0_Z ||
l.z == BelZ::MULT9X9_0_1_Z || l.z == BelZ::MULT9X9_1_0_Z || l.z == BelZ::MULT9X9_1_1_Z) {
if (explain_invalid) {
log_nonfatal_error(
"It is not possible to place the DSP so that the SOA register is on the macro boundary.\n");
}
return false;
}
}
if (bel_type.in(id_MULT9X9, id_PADD9)) {
int pair_z = gwu.get_dsp_paired_9(l.z);
const CellInfo *adj_dsp9 = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(l.x, l.y, pair_z)));
if (adj_dsp9 != nullptr) {
const auto &adj_dsp9_data = fast_cell_info.at(adj_dsp9->flat_index);
if ((dsp_data.dsp_asign != adj_dsp9_data.dsp_asign) || (dsp_data.dsp_bsign != adj_dsp9_data.dsp_bsign) ||
(dsp_data.dsp_asel != adj_dsp9_data.dsp_asel) || (dsp_data.dsp_bsel != adj_dsp9_data.dsp_bsel) ||
(dsp_data.dsp_reset != adj_dsp9_data.dsp_reset) || (dsp_data.dsp_ce != adj_dsp9_data.dsp_ce) ||
(dsp_data.dsp_clk != adj_dsp9_data.dsp_clk)) {
if (explain_invalid) {
log_nonfatal_error("For 9bit primitives the control signals must be same.\n");
}
return false;
}
}
}
// check for control nets "overflow"
BelId dsp_bel = ctx->getBelByLocation(Loc(l.x, l.y, BelZ::DSP_Z));
if (dsp_net_cnt.at(dsp_bel).reset.size() > 4) {
if (explain_invalid) {
log_nonfatal_error("More than 4 different networks for RESET signals in one DSP are not allowed.\n");
}
return false;
}
BelId dsp_macro_bel = ctx->getBelByLocation(Loc(l.x, l.y, gwu.get_dsp_macro(l.z)));
if (dsp_net_cnt.count(dsp_macro_bel)) {
if (dsp_net_cnt.at(dsp_macro_bel).ce.size() > 4 || dsp_net_cnt.at(dsp_macro_bel).clk.size() > 4) {
if (explain_invalid) {
log_nonfatal_error(
"More than 4 different networks for CE or CLK signals in one DSP macro are not allowed.\n");
}
return false;
}
}
return true;
}
bool GowinImpl::slice_valid(int x, int y, int z) const
{
const CellInfo *lut = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2)));
const bool lut_in_4_5 = lut && (z == 4 || z == 5);
const CellInfo *ff = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2 + 1)));
// There are only 6 ALUs
const CellInfo *alu = (z < 6) ? ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z + BelZ::ALU0_Z))) : nullptr;
const CellInfo *ramw =
(z == 4 || z == 5) ? ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, BelZ::RAMW_Z))) : nullptr;
if (alu && lut) {
return false;
}
if (ramw) {
if (alu || ff || lut_in_4_5) {
return false;
}
return true;
}
// check for ALU/LUT in the adjacent cell
int adj_lut_z = (1 - (z & 1) * 2 + z) * 2;
int adj_alu_z = adj_lut_z / 2 + BelZ::ALU0_Z;
const CellInfo *adj_lut = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, adj_lut_z)));
const CellInfo *adj_ff = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, adj_lut_z + 1)));
const CellInfo *adj_alu = adj_alu_z < (6 + BelZ::ALU0_Z)
? ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, adj_alu_z)))
: nullptr;
if ((alu && (adj_lut || (adj_ff && !adj_alu))) || ((lut || (ff && !alu)) && adj_alu)) {
return false;
}
// if there is DFF it must be connected to this LUT or ALU
if (ff) {
const auto &ff_data = fast_cell_info.at(ff->flat_index);
if (lut) {
const auto &lut_data = fast_cell_info.at(lut->flat_index);
if (ff_data.ff_d != lut_data.lut_f) {
return false;
}
}
if (alu) {
const auto &alu_data = fast_cell_info.at(alu->flat_index);
if (ff_data.ff_d != alu_data.alu_sum) {
return false;
}
}
if (adj_ff) {
if (incompatible_ffs(ff, adj_ff)) {
return false;
}
// CE, LSR and CLK must match
const auto &adj_ff_data = fast_cell_info.at(adj_ff->flat_index);
if (adj_ff_data.ff_lsr != ff_data.ff_lsr) {
return false;
}
if (adj_ff_data.ff_clk != ff_data.ff_clk) {
return false;
}
if (adj_ff_data.ff_ce != ff_data.ff_ce) {
return false;
}
}
}
return true;
}
// Cluster
bool GowinImpl::getClusterPlacement(ClusterId cluster, BelId root_bel,
std::vector<std::pair<CellInfo *, BelId>> &placement) const
{
CellInfo *root_ci = getClusterRootCell(cluster);
if (!root_ci->type.in(id_PADD9, id_MULT9X9, id_PADD18, id_MULT18X18, id_MULTALU18X18, id_MULTALU36X18,
id_MULTADDALU18X18, id_ALU54D)) {
return HimbaechelAPI::getClusterPlacement(cluster, root_bel, placement);
}
NPNR_ASSERT(root_bel != BelId());
if (!isValidBelForCellType(root_ci->type, root_bel)) {
return false;
}
IdString bel_type = ctx->getBelType(root_bel);
// non-chain DSP
if (root_ci->constr_children.size() == 1 && bel_type.in(id_PADD9, id_MULT9X9)) {
return HimbaechelAPI::getClusterPlacement(cluster, root_bel, placement);
}
placement.clear();
Loc root_loc = ctx->getBelLocation(root_bel);
placement.emplace_back(root_ci, root_bel);
Loc mult_loc = root_loc;
for (auto child : root_ci->constr_children) {
Loc child_loc;
child_loc.y = root_loc.y;
if (child->type == id_DUMMY_CELL) {
child_loc.x = mult_loc.x + child->constr_x;
child_loc.z = mult_loc.z + child->constr_z;
} else {
child_loc = gwu.get_dsp_next_in_chain(mult_loc, child->type);
mult_loc = child_loc;
}
BelId child_bel = ctx->getBelByLocation(child_loc);
if (child_bel == BelId() || !isValidBelForCellType(child->type, child_bel))
return false;
placement.emplace_back(child, child_bel);
}
return true;
}
void GowinImpl::notifyBelChange(BelId bel, CellInfo *cell)
{
if (cell != nullptr && !is_dsp(cell)) {
return;
}
if (cell == nullptr && dsp_bel2cell.count(bel) == 0) {
return;
}
// trace DSP control networks
IdString cell_type = id_DUMMY_CELL;
if (cell != nullptr) {
cell_type = cell->type;
}
Loc loc = ctx->getBelLocation(bel);
Loc l = loc;
l.z = gwu.get_dsp(loc.z);
BelId dsp = ctx->getBelByLocation(l);
l.z = gwu.get_dsp_macro(loc.z);
BelId dsp_macro = ctx->getBelByLocation(l);
if (cell) {
const auto &dsp_cell_data = fast_cell_info.at(cell->flat_index);
if (dsp_cell_data.dsp_reset != nullptr) {
dsp_net_cnt[dsp].reset[dsp_cell_data.dsp_reset->name]++;
}
if (dsp_cell_data.dsp_ce != nullptr) {
dsp_net_cnt[dsp_macro].ce[dsp_cell_data.dsp_ce->name]++;
}
if (dsp_cell_data.dsp_clk != nullptr) {
dsp_net_cnt[dsp_macro].clk[dsp_cell_data.dsp_clk->name]++;
}
dsp_bel2cell[bel] = cell;
} else {
const auto &dsp_cell_data = fast_cell_info.at(dsp_bel2cell.at(bel)->flat_index);
if (dsp_cell_data.dsp_reset != nullptr) {
dsp_net_cnt.at(dsp).reset.at(dsp_cell_data.dsp_reset->name)--;
}
if (dsp_cell_data.dsp_ce != nullptr) {
dsp_net_cnt.at(dsp_macro).ce.at(dsp_cell_data.dsp_ce->name)--;
}
if (dsp_cell_data.dsp_clk != nullptr) {
dsp_net_cnt.at(dsp_macro).clk.at(dsp_cell_data.dsp_clk->name)--;
}
dsp_bel2cell.erase(bel);
}
}
} // namespace
NEXTPNR_NAMESPACE_END