#include <map>
#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 "array2d.h"
#include "cst.h"
#include "globals.h"
#include "gowin.h"
#include "gowin_utils.h"
#include "pack.h"

#include "placer_heap.h"

NEXTPNR_NAMESPACE_BEGIN

namespace {
struct GowinImpl : HimbaechelAPI
{

    ~GowinImpl() {};
    po::options_description getUArchOptions() override;
    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;

    delay_t estimateDelay(WireId src, WireId dst) const 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;
    bool checkPipAvailForNet(PipId pip, const NetInfo *net) const override;

    // Cluster
    bool isClusterStrict(const CellInfo *cell) const override { return true; }
    bool getClusterPlacement(ClusterId cluster, BelId root_bel,
                             std::vector<std::pair<CellInfo *, BelId>> &placement) const override;

    void configurePlacerHeap(PlacerHeapCfg &cfg) override;

    void drawBel(std::vector<GraphicElement> &g, GraphicElement::style_t style, IdString bel_type, Loc loc) override;

  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;
        const NetInfo *dsp_5a_clk0 = nullptr, *dsp_5a_clk1 = nullptr, *dsp_5a_ce0 = nullptr, *dsp_5a_ce1 = nullptr,
                      *dsp_5a_reset0 = nullptr, *dsp_5a_reset1 = nullptr;
        bool dsp_soa_reg;
    };
    std::vector<GowinCellInfo> fast_cell_info;
    void assign_cell_info();

    // If there is an unused LUT adjacent to FF, use it
    void create_passthrough_luts(void);

    // 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
    // primitives use the same signals for CE, CLK and especially RESET, so
    // these wires are few and need to be controlled.
    struct dsp_info
    {
        dict<IdString, int> ce;    // CE nets counter
        dict<IdString, int> clk;   // CLK nets counter
        dict<IdString, int> reset; // RESET nets counter
        int mode9bit;              // 9 bit elements counter
        int mode18bit;             // 18 bit elements counter
    };
    dict<BelId, dsp_info> dsp_info;
    dict<BelId, CellInfo *> dsp_bel2cell; // Remember the connection with cells
                                          // 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();
    void place_5a_hclks(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;
    bool hclk_valid(BelId bel, IdString bel_type) const;

    array2d<std::vector<CellInfo *>> fast_logic_cell;

    delay_t delay_m, delay_c;
};

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) override { return std::make_unique<GowinImpl>(); }
} gowinArch;

po::options_description GowinImpl::getUArchOptions()
{
    po::options_description specific("Gowin specific options");
    specific.add_options()("family", po::value<std::string>(), "GOWIN chip family");
    specific.add_options()("cst", po::value<std::string>(), "name of constraints file");
    specific.add_options()("ireg_in_iob", "place input registers in IOB");
    specific.add_options()("oreg_in_iob", "place output registers in IOB");
    specific.add_options()("ioreg_in_iob", "place I/O registers in IOB");
    specific.add_options()("disable_gp_clock_routing", "disable clock network routing from GP pins");
    specific.add_options()("sspi_as_gpio", "use SSPI pins as GPIO");
    specific.add_options()("i2c_as_gpio", "use I2C pins as GPIO");
    return specific;
}

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["family"].as<std::string>();
    } 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("GW5A(T|ST)?-LV(25|60|138)[A-Z]*.*");
            std::smatch match;
            if (std::regex_match(args.device, match, devicere)) {
                family = stringf("GW5A%s-%s%s", match[1].str().c_str(), match[2].str().c_str(),
                                 match[2].str() == "25" ? "A" : "C");
            } 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->args;

    // 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]);
        ctx->set_speed_grade(match[2]);
    } else {
        if (pn.length() > 2 && pn.compare(pn.length() - 3, 2, "ES")) {
            package_idx = ctx->id(pn.substr(0, pn.length() - 2));
            spd = ctx->id("ES");
            ctx->set_speed_grade("ES");
        }
    }

    // log_info("search for %s, packages:%ld\n", package_idx.c_str(ctx), ctx->chip_info->packages.ssize());
    for (int i = 0; i < ctx->chip_info->packages.ssize(); ++i) {
        // log_info("i:%d %s\n", i, IdString(ctx->chip_info->packages[i].name).c_str(ctx));
        if (IdString(ctx->chip_info->packages[i].name) == package_idx) {
            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["cst"].as<std::string>();
    }

    // place registers in IO blocks
    if (args.options.count("ireg_in_iob")) {
        ctx->settings[id_IREG_IN_IOB] = Property(1);
    }
    if (args.options.count("oreg_in_iob")) {
        ctx->settings[id_OREG_IN_IOB] = Property(1);
    }
    if (args.options.count("ioreg_in_iob")) {
        ctx->settings[id_IOREG_IN_IOB] = Property(1);
    }

    // smart clock routing
    if (args.options.count("disable_gp_clock_routing")) {
        ctx->settings[id_NO_GP_CLOCK_ROUTING] = Property(1);
    }

    // configure delay estimates for A*
    if (args.device.rfind("GW2A", 0) == 0 || args.device.rfind("GW5A", 0) == 0) {
        delay_c = 300;
        delay_m = 60;
        ctx->ripup_penalty = 400;
    } else {
        delay_c = 600;
        delay_m = 120;
        ctx->ripup_penalty = 800;
    }
}

// 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) || gwu.is_segment_pip(pip)));
}

bool GowinImpl::checkPipAvailForNet(PipId pip, const NetInfo *net) const
{
    return (net->constant_value == IdString() && ctx->getWireConstantValue(ctx->getPipSrcWire(pip)) != IdString()) ||
           (!(gwu.is_global_pip(pip) || gwu.is_segment_pip(pip)));
}

void GowinImpl::pack()
{
    if (ctx->settings.count(ctx->id("cst.filename"))) {
        std::string filename = ctx->settings[ctx->id("cst.filename")].as_string();
        auto in = open_ifstream_and_log_error(filename, "CST file");

        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)
{
    if (gwu.has_5A_DSP()) {
        return;
    }
    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_info.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_info.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_info.at(dsp).clk, id_CLK, dsp_data.dsp_clk->name, "CLK%d",
                             ci->type == id_MULT36X36 ? "CLK%d%d" : nullptr);
        }
    }
}

// GW5A HCLK
// Each serializer/deserializer can use one of several HCLK wire from a block
// (hclk_idx), but only from the specific block assigned to that
// serializer/deserializer.
// Each HCLK line can be routed through a CLKDIV2 primitive, which halves the signal frequency.
//
// Thus, if the SERDES uses CLKDIV2, the latter must be placed in the specific
// HCLK block corresponding to the SERDES (since the SERDES is part of the I/O,
// and we do not allow unconstrained I/O, the placement of the SERDES is always
// known).
//
// Since users typically do not track which pins belong to which HCLK block,
// situations may arise where a single CLKDIV2 divider in the design serves as
// the signal source for SERDES located in different HCLK blocks, making it
// impossible to connect them. Alternatively, some SERDES in one block may
// receive the signal directly, while others receive it from the CLKDIV2.
//
// Here, we solve this by duplicating the CLKDIV2 cells and placing them
// exactly where they serve specific HCLK blocks.
//
// Information regarding which IO corresponds to a specific HCLK, as well as
// the placement of CLKDIV2 for that specific HCLK, is retrieved from the
// chip's database.
//
// CLKDIV can be used either on its own or in conjunction with CLKDIV2;
// however, in the latter case, there is only one CLKDIV2->CLKDIV connection,
// and it is non-switchable. This makes it necessary to clone CLKDIV2 when
// using it with multiple CLKDIVs.
//
void GowinImpl::place_5a_hclks(void)
{
    std::vector<std::unique_ptr<CellInfo>> new_cells;
    // Cloned CLKDIV2 cells
    dict<int, CellInfo *> clkdiv2_clones;
    // CLKDIV cells
    std::vector<CellInfo *> clkdiv_list;
    // Which users need to be reconnected, and to where.
    std::vector<std::pair<PortRef, CellInfo *>> users_to_reconect;
    // CLKDIV2 allocator
    dict<int, std::vector<Loc>> free_clkdiv2;

    // SERDES can use any wire from the HCLK block; here, we select a wire from
    // the unused ones and return the CLKDIV2 location that serves that wire.
    auto alloc_clkdiv2 = [&](int hclk_idx, CellInfo *ci) -> Loc {
        // first allocation for hclk_idx
        if (!free_clkdiv2.count(hclk_idx)) {
            std::vector<Loc> locs;
            gwu.get_clkdiv2_locs(hclk_idx, locs);
            free_clkdiv2[hclk_idx] = locs;
        }

        if (!free_clkdiv2.at(hclk_idx).size()) {
            log_error("Can't place %s CLKDIV2.\n", ctx->nameOf(ci));
        }
        Loc loc = free_clkdiv2.at(hclk_idx).back();
        free_clkdiv2.at(hclk_idx).pop_back();
        return loc;
    };

    // If CLKDIV is used together with CLKDIV2, we should place them together.
    auto make_clkdiv2_clkdiv_cluster = [&](CellInfo *ci, CellInfo *clkdiv) -> void {
        if (ctx->debug) {
            log_info("  Make cluster from %s and %s.\n", ctx->nameOf(ci), ctx->nameOf(clkdiv));
        }

        ci->cluster = ci->name;
        ci->constr_abs_z = false;
        ci->constr_children.push_back(clkdiv);

        clkdiv->cluster = ci->name;
        clkdiv->constr_abs_z = false;
        clkdiv->constr_x = 0;
        clkdiv->constr_y = 0;
        clkdiv->constr_z = BelZ::CLKDIV_0_Z - BelZ::CLKDIV2_0_Z;
    };

    for (auto &cell : ctx->cells) {
        auto ci = cell.second.get();

        // The CLKDIV2s described in the design
        if (is_clkdiv2(ci)) {
            NetInfo *hclk_net = ci->getPort(id_CLKOUT);
            if (!hclk_net || !ci->getPort(id_HCLKIN)) {
                continue;
            }
            if (ctx->debug) {
                log_info("  CLKDIV2 cell:%s, HCLKIN:%s, CLKOUT:%s\n", ctx->nameOf(ci),
                         ctx->nameOf(ci->getPort(id_HCLKIN)), ctx->nameOf(hclk_net));
            }

            clkdiv2_clones.clear();
            clkdiv_list.clear();
            users_to_reconect.clear();
            int cur_clkdiv2_hclk_idx = -1;

            // CLKDIV and SERDES only
            for (auto user : hclk_net->users) {
                if (is_clkdiv(user.cell)) {
                    clkdiv_list.push_back(user.cell);
                }
                if (!is_iologico(user.cell) && !is_iologici(user.cell)) {
                    continue;
                }
                // checking users' hclk index
                NPNR_ASSERT(user.cell->bel != BelId());
                Loc user_loc = ctx->getBelLocation(user.cell->bel);
                int hclk_idx = gwu.get_hclk_for_io(user_loc);
                if (hclk_idx == -1) {
                    log_error("%s can't use HCLK with %s.\n", ctx->nameOf(user.cell), ctx->nameOf(ci));
                }

                if (cur_clkdiv2_hclk_idx == -1) {
                    // Place CLKDIV2
                    cur_clkdiv2_hclk_idx = hclk_idx;
                    BelId bel = ctx->getBelByLocation(alloc_clkdiv2(hclk_idx, ci));
                    ctx->bindBel(bel, ci, PlaceStrength::STRENGTH_LOCKED);
                    if (ctx->debug) {
                        log_info("  @%s\n", ctx->nameOfBel(bel));
                    }
                    if (ctx->debug) {
                        log_info("    hclk:%d - %s %s\n", hclk_idx, ctx->nameOfBel(user.cell->bel),
                                 ctx->nameOf(user.cell));
                    }
                    continue;
                }

                // If the SERDES is in the current HCLK block, it remains there
                if (cur_clkdiv2_hclk_idx == hclk_idx) {
                    if (ctx->debug) {
                        log_info("    hclk:%d - %s %s\n", hclk_idx, ctx->nameOfBel(user.cell->bel),
                                 ctx->nameOf(user.cell));
                    }
                    continue;
                }

                // Since the SERDES is located in a different HCLK block, we
                // need to create a copy of CLKDIV2 and save the SERDES
                // for later connection to the copy.
                CellInfo *new_clkdiv2;
                if (clkdiv2_clones.count(hclk_idx)) {
                    // already have clone
                    new_clkdiv2 = clkdiv2_clones.at(hclk_idx);
                } else {
                    // create clone
                    new_cells.push_back(gwu.create_cell(gwu.create_aux_name(ci->name, hclk_idx), id_CLKDIV2));
                    new_clkdiv2 = new_cells.back().get();
                    new_clkdiv2->addInput(id_HCLKIN);
                    new_clkdiv2->addOutput(id_CLKOUT);
                    clkdiv2_clones[hclk_idx] = new_clkdiv2;
                    if (ctx->debug) {
                        log_info("    create clone for hclk:%d - %s\n", hclk_idx, ctx->nameOf(new_clkdiv2));
                    }
                }
                users_to_reconect.push_back(std::make_pair(user, new_clkdiv2));
            }
            // move the SERDES by connecting it to a copy of CLKDIV2
            for (auto us_ci : users_to_reconect) {
                PortRef user = us_ci.first;
                CellInfo *new_clkdiv2 = us_ci.second;
                if (ctx->debug) {
                    log_info("   reconnect %s.%s to %s\n", ctx->nameOf(user.cell), user.port.c_str(ctx),
                             ctx->nameOf(new_clkdiv2));
                }
                // input is same
                if (!new_clkdiv2->getPort(id_HCLKIN)) {
                    ci->copyPortTo(id_HCLKIN, new_clkdiv2, id_HCLKIN);
                }
                // move user
                user.cell->disconnectPort(user.port);
                new_clkdiv2->connectPorts(id_CLKOUT, user.cell, user.port);
            }

            // Place CLKDIV
            if (clkdiv_list.size()) {
                // First, the most common configuration: a single CLKDIV connected to former CLKDIV2
                CellInfo *clkdiv = clkdiv_list.back();
                clkdiv_list.pop_back();

                // Place CLKDIV only if CLKDIV2 is placed
                if (ci->bel != BelId()) {
                    Loc loc = ctx->getBelLocation(ci->bel);
                    loc.z = loc.z - BelZ::CLKDIV2_0_Z + BelZ::CLKDIV_0_Z;
                    BelId bel = ctx->getBelByLocation(loc);
                    ctx->bindBel(bel, clkdiv, PlaceStrength::STRENGTH_LOCKED);
                } else {
                    make_clkdiv2_clkdiv_cluster(ci, clkdiv);
                }

                // Connect the remaining CLKDIVs to the clones
                int name_sfx = 0;
                for (auto clkdiv : clkdiv_list) {
                    CellInfo *clone;
                    // If we have free clones
                    if (clkdiv2_clones.size()) {
                        clone = clkdiv2_clones.begin()->second;
                        clkdiv2_clones.erase(clkdiv2_clones.begin());
                    } else {
                        // create clone
                        ++name_sfx;
                        new_cells.push_back(
                                gwu.create_cell(gwu.create_aux_name(ci->name, name_sfx, "$for_clkdiv"), id_CLKDIV2));
                        clone = new_cells.back().get();
                        clone->addInput(id_HCLKIN);
                        clone->addOutput(id_CLKOUT);
                        // input is same
                        ci->copyPortTo(id_HCLKIN, clone, id_HCLKIN);
                    }
                    clkdiv->disconnectPort(id_HCLKIN);
                    clone->connectPorts(id_CLKOUT, clkdiv, id_HCLKIN);
                }
            }
        }
    }

    // Place new CLKDIV2
    for (auto &ncell : new_cells) {
        CellInfo *ci = ncell.get();
        NetInfo *hclk_net = ci->getPort(id_CLKOUT);
        if (ctx->debug) {
            log_info("  CLKDIV2 cell:%s, HCLKIN:%s, CLKOUT:%s\n", ctx->nameOf(ci), ctx->nameOf(ci->getPort(id_HCLKIN)),
                     ctx->nameOf(hclk_net));
        }

        // If we have only one user, and that user is CLKDIV, then there are no
        // strict restrictions on location; the only requirement is that they
        // be co-located — so we create a cluster.
        if (hclk_net->users.entries() == 1 && is_clkdiv((*hclk_net->users.begin()).cell)) {
            make_clkdiv2_clkdiv_cluster(ci, (*hclk_net->users.begin()).cell);
        } else {
            // Any user can be used to determine hclk_idx because we connected them
            // to copies of CLKDIV2 based precisely on the fact that hclk_idx is
            // the same
            PortRef &user = *hclk_net->users.begin();
            Loc user_loc = ctx->getBelLocation(user.cell->bel);
            int hclk_idx = gwu.get_hclk_for_io(user_loc);

            BelId bel = ctx->getBelByLocation(alloc_clkdiv2(hclk_idx, ci));
            ctx->bindBel(bel, ci, PlaceStrength::STRENGTH_LOCKED);
            if (ctx->debug) {
                log_info("  @%s\n", ctx->nameOfBel(bel));
                log_info("    hclk:%d - %s %s\n", hclk_idx, ctx->nameOfBel(user.cell->bel), ctx->nameOf(user.cell));
            }
            // Place CLKDIV if any
            for (auto user : hclk_net->users) {
                if (is_clkdiv(user.cell)) {
                    Loc loc = ctx->getBelLocation(ci->bel);
                    loc.z = loc.z - BelZ::CLKDIV2_0_Z + BelZ::CLKDIV_0_Z;
                    BelId bel = ctx->getBelByLocation(loc);
                    ctx->bindBel(bel, user.cell, PlaceStrength::STRENGTH_LOCKED);
                }
            }
        }
        ctx->cells[ncell->name] = std::move(ncell);
    }
}

/*
   Each HCLK section can serve one of three purposes:
       1. A simple routing path to IOLOGIC FCLK or PLL inputs
       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");
    if (gwu.has_5A_HCLK()) {
        place_5a_hclks();
        return;
    }

    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;

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

        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_hclk_user(ci)) {
            get_nets(ci);
            for (auto &hclk_net : net_list) {
                if (!hclk_net) {
                    continue;
                }
                CellInfo *hclk_driver = hclk_net->driver.cell;
                if (!hclk_driver)
                    continue;
                if (!(chip_has_clkdiv_hclk_connection || hclk_driver->type == id_CLKDIV2)) {
                    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 (!is_hclk_user(user.cell)) {
                        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));
                    }

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

    // 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();
    ctx->assignArchInfo();
    assign_cell_info();
    fast_logic_cell.reset(ctx->getGridDimX(), ctx->getGridDimY());
    for (auto bel : ctx->getBels()) {
        if (ctx->getBelType(bel) == id_LUT4) {
            Loc loc = ctx->getBelLocation(bel);
            fast_logic_cell.at(loc.x, loc.y).resize(37);
        }
    }
}

void GowinImpl::postPlace()
{
    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();
    create_passthrough_luts();
}

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);
                        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 (!gwu.has_5A_HCLK()) {
                            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));
                                }
                            }
                        } else if (up_wire_name.in(id_HCLK00, id_HCLK10, id_HCLK20, id_HCLK30)) {
                            user.cell->setAttr(id_IOLOGIC_FCLK, Property("HCLK_OUT0"));
                        } else if (up_wire_name.in(id_HCLK01, id_HCLK11, id_HCLK21, id_HCLK31)) {
                            user.cell->setAttr(id_IOLOGIC_FCLK, Property("HCLK_OUT1"));
                        } else if (up_wire_name.in(id_HCLK02, id_HCLK12, id_HCLK22, id_HCLK32)) {
                            user.cell->setAttr(id_IOLOGIC_FCLK, Property("HCLK_OUT2"));
                        } else if (up_wire_name.in(id_HCLK03, id_HCLK13, id_HCLK23, id_HCLK33)) {
                            user.cell->setAttr(id_IOLOGIC_FCLK, Property("HCLK_OUT3"));
                        }
                        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)));
                        }
                    }
                }
            }
        } 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"));
                    }
                }
            }
        }
    }
    std::vector<CellInfo *> to_remove;
    for (auto &cell : ctx->cells) {
        CellInfo *ci = cell.second.get();
        if (ci->type.in(id_BLOCKER_LUT, id_BLOCKER_FF)) {
            to_remove.push_back(ci);
        }
    }
    for (auto ci : to_remove) {
        auto root = ctx->cells.at(ci->cluster).get();
        root->constr_children.erase(std::remove_if(root->constr_children.begin(), root->constr_children.end(),
                                                   [&](CellInfo *c) { return c == ci; }));
        ctx->cells.erase(ci->name);
    }
}

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:
        return slice_valid(l.x, l.y, 0);
    case ID_MUX2_LUT5:
        return slice_valid(l.x, l.y, (l.z - BelZ::MUX20_Z) / 2);
    case ID_MUX2_LUT6:
        return slice_valid(l.x, l.y, (l.z - BelZ::MUX21_Z) / 2 + 1);
    case ID_MUX2_LUT7:
        return slice_valid(l.x, l.y, 3);
    case ID_MUX2_LUT8:
        return slice_valid(l.x, l.y, 7);
    case ID_PADD9:           /* fall-through */
    case ID_PADD18:          /* fall-through */
    case ID_MULT9X9:         /* fall-through */
    case ID_MULT12X12:       /* 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);
    case ID_CLKDIV2: /* fall-through */
    case ID_CLKDIV:
        if (gwu.has_5A_HCLK()) {
            return true;
        }
        return hclk_valid(bel, bel_type);
    }
    return true;
}

// Bel bucket functions
IdString GowinImpl::getBelBucketForCellType(IdString cell_type) const
{
    if (cell_type.in(id_IBUF, id_OBUF)) {
        return id_IOB;
    }
    if (cell_type.in(id_MIPI_OBUF, id_MIPI_OBUF_A)) {
        return id_MIPI_OBUF;
    }
    if (type_is_lut(cell_type) || cell_type == id_BLOCKER_LUT) {
        return id_LUT4;
    }
    if (type_is_dff(cell_type) || cell_type == id_BLOCKER_FF) {
        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_MIPI_OBUF) {
        return cell_type.in(id_MIPI_OBUF, id_MIPI_OBUF_A);
    }
    if (bel_type == id_LUT4) {
        return type_is_lut(cell_type) || cell_type == id_BLOCKER_LUT;
    }
    if (bel_type == id_DFF) {
        return type_is_dff(cell_type) || cell_type == id_BLOCKER_FF;
    }
    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;
            fc.dsp_5a_clk0 = get_net(id_CLK0);
            fc.dsp_5a_clk1 = get_net(id_CLK1);
            fc.dsp_5a_ce0 = get_net(id_CE0);
            fc.dsp_5a_ce1 = get_net(id_CE1);
            fc.dsp_5a_reset0 = get_net(id_RESET0);
            fc.dsp_5a_reset1 = get_net(id_RESET1);
        }
    }
}

// If there is an unused LUT next to the DFF, use its inputs for the D input
void GowinImpl::create_passthrough_luts(void)
{
    std::vector<std::unique_ptr<CellInfo>> new_cells;
    for (auto &cell : ctx->cells) {
        CellInfo *ci = cell.second.get();
        if (is_dff(ci)) {
            Loc loc = ctx->getBelLocation(ci->bel);
            BelId lut_bel = ctx->getBelByLocation(Loc(loc.x, loc.y, loc.z - 1));
            CellInfo *lut = ctx->getBoundBelCell(lut_bel);
            CellInfo *alu = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(loc.x, loc.y, loc.z / 2 + BelZ::ALU0_Z)));
            const CellInfo *ramw = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(loc.x, loc.y, BelZ::RAMW_Z)));

            if (!(lut || alu || ramw)) {
                if (ctx->debug) {
                    log_info("Found an unused LUT:%s, ", ctx->nameOfBel(lut_bel));
                }
                // make LUT
                auto lut_cell = gwu.create_cell(gwu.create_aux_name(ci->name, 0, "_passthrough_lut$"), id_LUT4);
                CellInfo *lut = lut_cell.get();
                NetInfo *d_net = ci->getPort(id_D);
                NPNR_ASSERT(d_net != nullptr);

                if (d_net->name == ctx->id("$PACKER_GND") || d_net->name == ctx->id("$PACKER_VCC")) {
                    if (ctx->debug) {
                        log_info("make a constant %s.\n", d_net->name == ctx->id("$PACKER_VCC") ? "VCC" : "GND");
                    }
                    ci->disconnectPort(id_D);
                    if (d_net->name == ctx->id("$PACKER_GND")) {
                        lut->setParam(id_INIT, 0x0000);
                    } else {
                        lut->setParam(id_INIT, 0xffff);
                    }
                } else {
                    if (ctx->debug) {
                        log_info("make a pass-through.\n");
                    }
                    IdString lut_input = id_I3;
                    int lut_init = 0xff00;

                    lut->addInput(lut_input);
                    lut->cell_bel_pins[lut_input].clear();
                    lut->cell_bel_pins.at(lut_input).push_back(lut_input);
                    ci->movePortTo(id_D, lut, lut_input);
                    lut->setParam(id_INIT, lut_init);
                }
                lut->addOutput(id_F);
                lut->cell_bel_pins[id_F].clear();
                lut->cell_bel_pins.at(id_F).push_back(id_F);
                ci->connectPorts(id_D, lut, id_F);

                ctx->bindBel(lut_bel, lut, PlaceStrength::STRENGTH_LOCKED);
                new_cells.push_back(std::move(lut_cell));
            }
        }
    }
    for (auto &cell : new_cells) {
        ctx->cells[cell->name] = std::move(cell);
    }
}

// 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) || (ff->type == id_DFF && adj_ff->type != id_DFF) ||
            (ff->type == id_DFFE && adj_ff->type != id_DFFE) || (ff->type == id_DFFN && adj_ff->type != id_DFFN) ||
            (ff->type == id_DFFNE && adj_ff->type != id_DFFNE));
}

// 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);

    BelId dsp_macro_bel = ctx->getBelByLocation(Loc(l.x, l.y, gwu.get_dsp_macro(l.z)));
    if (dsp_info.count(dsp_macro_bel)) {
        if (dsp_info.at(dsp_macro_bel).mode9bit && dsp_info.at(dsp_macro_bel).mode18bit) {
            if (explain_invalid) {
                log_nonfatal_error("Different operand lengths (9 and 18) are not permitted in one DSP macro.\n");
            }
            return false;
        }
    }

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

    if (bel_type == id_MULT12X12) {
        int pair_z = gwu.get_dsp_paired_12(l.z);
        const CellInfo *adj_dsp12 = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(l.x, l.y, pair_z)));
        if (adj_dsp12 != nullptr) {
            const auto &adj_dsp12_data = fast_cell_info.at(adj_dsp12->flat_index);
            if ((dsp_data.dsp_5a_clk0 != adj_dsp12_data.dsp_5a_clk0) ||
                (dsp_data.dsp_5a_clk1 != adj_dsp12_data.dsp_5a_clk1) ||
                (dsp_data.dsp_5a_ce0 != adj_dsp12_data.dsp_5a_ce0) ||
                (dsp_data.dsp_5a_ce1 != adj_dsp12_data.dsp_5a_ce1) ||
                (dsp_data.dsp_5a_reset0 != adj_dsp12_data.dsp_5a_reset0) ||
                (dsp_data.dsp_5a_reset1 != adj_dsp12_data.dsp_5a_reset1)) {
                if (explain_invalid) {
                    log_nonfatal_error("For MULT12X12 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_info.count(dsp_bel)) {
        if (dsp_info.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;
        }
    }
    if (dsp_info.count(dsp_macro_bel)) {
        if (dsp_info.at(dsp_macro_bel).ce.size() > 4 || dsp_info.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
{
    auto &bels = fast_logic_cell.at(x, y);
    const CellInfo *lut = bels.at(z * 2);
    const CellInfo *ff = bels.at(z * 2 + 1);
    // There are only 6 ALUs
    const CellInfo *alu = (z < 6) ? bels.at(z + BelZ::ALU0_Z) : nullptr;
    const CellInfo *ramw = bels.at(BelZ::RAMW_Z);

    auto is_not_blocker = [](const CellInfo *ci) { return ci && !ci->type.in(id_BLOCKER_LUT, id_BLOCKER_FF); };

    if (alu && lut && lut->type != id_BLOCKER_LUT) {
        return false;
    }

    if (ramw) {
        // FFs in slices 4 and 5 are not allowed
        // also temporarily disallow FF to be placed near RAM
        if (is_not_blocker(bels.at(0 * 2 + 1)) || is_not_blocker(bels.at(1 * 2 + 1)) ||
            is_not_blocker(bels.at(2 * 2 + 1)) || is_not_blocker(bels.at(3 * 2 + 1)) ||
            is_not_blocker(bels.at(4 * 2 + 1)) || is_not_blocker(bels.at(5 * 2 + 1))) {
            return false;
        }
        if (gwu.has_DFF67()) {
            if (is_not_blocker(bels.at(6 * 2 + 1)) || is_not_blocker(bels.at(7 * 2 + 1))) {
                return false;
            }
        }
        // ALU/LUTs in slices 4, 5, 6, 7 are not allowed
        for (int i = 4; i < 8; ++i) {
            if (is_not_blocker(bels.at(i * 2))) {
                return false;
            }
            if (i < 6 && bels.at(i + BelZ::ALU0_Z)) {
                return false;
            }
        }
    }

    // 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 = bels.at(adj_lut_z);
    const CellInfo *adj_ff = bels.at(adj_lut_z + 1);
    const CellInfo *adj_alu = adj_alu_z < (6 + BelZ::ALU0_Z) ? bels.at(adj_alu_z) : nullptr;

    if ((alu && ((adj_lut && adj_lut->type != id_BLOCKER_LUT) || (adj_ff && !adj_alu))) ||
        (((lut && lut->type != id_BLOCKER_LUT) || (ff && !alu)) && adj_alu)) {
        return false;
    }

    if (ff && ff->type != id_BLOCKER_FF) {
        static std::vector<int> mux_z = {BelZ::MUX20_Z,     BelZ::MUX21_Z,     BelZ::MUX20_Z + 4,  BelZ::MUX23_Z,
                                         BelZ::MUX20_Z + 8, BelZ::MUX21_Z + 8, BelZ::MUX20_Z + 12, BelZ::MUX27_Z};
        const auto &ff_data = fast_cell_info.at(ff->flat_index);
        const NetInfo *src;
        // check implcit LUT(ALU) -> FF connection
        NPNR_ASSERT(!ramw); // XXX shouldn't happen for now
        if (lut || alu) {
            if (lut && lut->type != id_BLOCKER_LUT) {
                src = fast_cell_info.at(lut->flat_index).lut_f;
            } else {
                src = fast_cell_info.at(alu->flat_index).alu_sum;
            }
            if (ff_data.ff_d != src) {
                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;
            }
        }
        // Check whether the current architecture allows 6 and 7 DFFs
        if (z > 3 && gwu.has_DFF67()) {
            // The 4th, 5th, 6th, and 7th DFFs have the same control wires. Let's check this.
            const int adj_top_ff_z = (5 - (z >> 1)) * 4 + 1;
            for (int i = 0; i < 4; i += 2) {
                const CellInfo *adj_top_ff = bels.at(adj_top_ff_z + i);
                if (adj_top_ff) {
                    const auto &adj_top_ff_data = fast_cell_info.at(adj_top_ff->flat_index);
                    if (adj_top_ff_data.ff_lsr != ff_data.ff_lsr) {
                        return false;
                    }
                    if (adj_top_ff_data.ff_clk != ff_data.ff_clk) {
                        return false;
                    }
                    if (adj_top_ff_data.ff_ce != ff_data.ff_ce) {
                        return false;
                    }
                    // It is sufficient to check only one DFF.
                    break;
                }
            }
        }
    }
    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,
                                    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, id_MULTADDALU12X12, id_MULTALU27X18)) {
        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)
{

    IdString bel_type = ctx->getBelType(bel);
    switch (bel_type.hash()) {
    case ID_LUT4: /* fall-through */
    case ID_DFF:
    case ID_ALU:
    case ID_RAM16SDP4:
    case ID_MUX2_LUT5:
    case ID_MUX2_LUT6:
    case ID_MUX2_LUT7:
    case ID_MUX2_LUT8:
        auto loc = ctx->getBelLocation(bel);
        fast_logic_cell.at(loc.x, loc.y).at(loc.z) = cell;
        return;
    }

    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) {
        bool mode9 = cell_type.in(id_PADD9, id_MULT9X9);
        if (mode9) {
            dsp_info[dsp_macro].mode9bit++;
        } else {
            dsp_info[dsp_macro].mode18bit++;
        }

        const auto &dsp_cell_data = fast_cell_info.at(cell->flat_index);
        if (dsp_cell_data.dsp_reset != nullptr) {
            dsp_info[dsp].reset[dsp_cell_data.dsp_reset->name]++;
        }
        if (dsp_cell_data.dsp_ce != nullptr) {
            dsp_info.at(dsp_macro).ce[dsp_cell_data.dsp_ce->name]++;
        }
        if (dsp_cell_data.dsp_clk != nullptr) {
            dsp_info.at(dsp_macro).clk[dsp_cell_data.dsp_clk->name]++;
        }
        dsp_bel2cell[bel] = cell;
    } else {
        bool mode9 = dsp_bel2cell.at(bel)->type.in(id_PADD9, id_MULT9X9);
        if (mode9) {
            dsp_info.at(dsp_macro).mode9bit--;
        } else {
            dsp_info.at(dsp_macro).mode18bit--;
        }

        const auto &dsp_cell_data = fast_cell_info.at(dsp_bel2cell.at(bel)->flat_index);
        if (dsp_cell_data.dsp_reset != nullptr) {
            dsp_info.at(dsp).reset.at(dsp_cell_data.dsp_reset->name)--;
        }
        if (dsp_cell_data.dsp_ce != nullptr) {
            dsp_info.at(dsp_macro).ce.at(dsp_cell_data.dsp_ce->name)--;
        }
        if (dsp_cell_data.dsp_clk != nullptr) {
            dsp_info.at(dsp_macro).clk.at(dsp_cell_data.dsp_clk->name)--;
        }
        dsp_bel2cell.erase(bel);
    }
}

void GowinImpl::configurePlacerHeap(PlacerHeapCfg &cfg)
{
    // Use cell groups to enforce a legalisation order
    cfg.cellGroups.emplace_back();
    cfg.cellGroups.back().insert(id_RAM16SDP4);
    cfg.cellGroups.emplace_back();
    cfg.cellGroups.back().insert(id_ALU);

    cfg.placeAllAtOnce = true;

    // Treat control and constants like IO buffers, because they have only one possible location
    cfg.ioBufTypes.insert(id_GOWIN_VCC);
    cfg.ioBufTypes.insert(id_GOWIN_GND);
    cfg.ioBufTypes.insert(id_PINCFG);
    cfg.ioBufTypes.insert(id_GSR);
}

void GowinImpl::drawBel(std::vector<GraphicElement> &g, GraphicElement::style_t style, IdString bel_type, Loc loc)
{
    GraphicElement el;
    el.type = GraphicElement::TYPE_BOX;
    el.style = style;
    switch (bel_type.index) {
    case id_LUT4.index:
        el.x1 = loc.x + 0.75;
        el.x2 = el.x1 + 0.08;
        el.y1 = loc.y + 0.1 + 0.1 * (loc.z / 2);
        el.y2 = el.y1 + 0.04;
        g.push_back(el);
        break;
    case id_ALU.index:
        el.x1 = loc.x + 0.75;
        el.x2 = el.x1 + 0.08;
        el.y1 = loc.y + 0.1 + 0.1 * (loc.z - BelZ::ALU0_Z);
        el.y2 = el.y1 + 0.04;
        g.push_back(el);
        break;
    case id_DFF.index:
        el.x1 = loc.x + 0.9;
        el.x2 = el.x1 + 0.02;
        el.y1 = loc.y + 0.1 + 0.1 * (loc.z / 2);
        el.y2 = el.y1 + 0.04;
        g.push_back(el);
        break;
    case id_RAM16SDP4.index:
        el.x1 = loc.x + 0.85;
        el.x2 = el.x1 + 0.01;
        el.y1 = loc.y + 0.1;
        el.y2 = el.y1 + 0.65;
        g.push_back(el);
        break;
    }
}

delay_t GowinImpl::estimateDelay(WireId src, WireId dst) const
{
    int sx, sy, dx, dy;
    tile_xy(ctx->chip_info, src.tile, sx, sy);
    tile_xy(ctx->chip_info, dst.tile, dx, dy);
    int dist_x = std::abs(dx - sx), dist_y = std::abs(dy - sy);
    return delay_c + delay_m * (std::max(dist_x - 4, 0) + std::max(dist_y - 4, 0) +
                                2 * (std::min(dist_x, 4) + std::min(dist_y, 4)));
}

} // namespace

NEXTPNR_NAMESPACE_END
