#include "log.h"
#include "nextpnr.h"
#include "util.h"

#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
#include "himbaechel_constids.h"
#include "himbaechel_helpers.h"

#include <queue>
#include "gowin.h"
#include "gowin_utils.h"

NEXTPNR_NAMESPACE_BEGIN

// Remove [ and ] from port names
void GowinUtils::remove_brackets(CellInfo *ci)
{
    std::vector<IdString> orig_port_names;
    for (auto &port : ci->ports) {
        orig_port_names.push_back(port.first);
    }

    for (auto pname : orig_port_names) {
        IdString new_name;
        std::string stripped_name;

        for (auto c : pname.str(ctx)) {
            if (c != '[' && c != ']') {
                stripped_name += c;
            }
        }
        new_name = ctx->id(stripped_name);
        if (new_name != pname) {
            ci->renamePort(pname, new_name);
        }
    }
}

// clock sources
bool GowinUtils::driver_is_clksrc(const PortRef &driver)
{
    // dedicated pins
    if (CellTypePort(driver) == CellTypePort(id_IBUF, id_O)) {

        NPNR_ASSERT(driver.cell->bel != BelId());
        IdStringList pin_func = get_pin_funcs(driver.cell->bel);
        for (size_t i = 0; i < pin_func.size(); ++i) {
            if (ctx->debug) {
                log_info("bel:%s, pin func: %zu:%s\n", ctx->nameOfBel(driver.cell->bel), i,
                         pin_func[i].str(ctx).c_str());
            }
            if (pin_func[i].str(ctx).rfind("GCLKT", 0) == 0) {
                if (ctx->debug) {
                    log_info("Clock pin:%s:%s\n", ctx->getBelName(driver.cell->bel).str(ctx).c_str(),
                             pin_func[i].c_str(ctx));
                }
                return true;
            }
        }
    }
    // OSC outputs
    if (driver.cell->type.in(id_OSC, id_OSCA, id_OSCZ, id_OSCH, id_OSCF, id_OSCW, id_OSCO)) {
        if (driver.port == id_OSCOUT) {
            if (ctx->debug) {
                if (driver.cell->bel != BelId()) {
                    log_info("OSC out bel:%s:%s\n", ctx->nameOfBel(driver.cell->bel), driver.port.c_str(ctx));
                } else {
                    log_info("OSC out:%s:%s\n", ctx->nameOf(driver.cell), driver.port.c_str(ctx));
                }
            }
            return true;
        }
    }
    // PLL outputs
    if (driver.cell->type.in(id_rPLL, id_PLLVR, id_PLLA)) {
        if (driver.port.in(id_CLKOUT, id_CLKOUTD, id_CLKOUTD3, id_CLKOUTP, id_CLKOUT0, id_CLKOUT1, id_CLKOUT2,
                           id_CLKOUT3, id_CLKOUT4, id_CLKOUT5, id_CLKOUT6, id_CLKOUT7)) {
            if (ctx->debug) {
                if (driver.cell->bel != BelId()) {
                    log_info("PLL out bel:%s:%s\n", ctx->nameOfBel(driver.cell->bel), driver.port.c_str(ctx));
                } else {
                    log_info("PLL out:%s:%s\n", ctx->nameOf(driver.cell), driver.port.c_str(ctx));
                }
            }
            return true;
        }
    }
    // HCLK outputs
    if (driver.cell->type.in(id_CLKDIV, id_CLKDIV2)) {
        if (driver.port.in(id_CLKOUT)) {
            if (ctx->debug) {
                if (driver.cell->bel != BelId()) {
                    log_info("%s out bel:%s:%s:%s\n", driver.cell->type.c_str(ctx),
                             ctx->getBelName(driver.cell->bel).str(ctx).c_str(), driver.port.c_str(ctx),
                             ctx->nameOfWire(ctx->getBelPinWire(driver.cell->bel, driver.port)));
                } else {
                    log_info("%s out:%s:%s\n", driver.cell->type.c_str(ctx), ctx->nameOf(driver.cell),
                             driver.port.c_str(ctx));
                }
            }
            return true;
        }
    }
    // DLLDLY outputs
    if (driver.cell->type == id_DLLDLY) {
        if (driver.port.in(id_CLKOUT)) {
            if (ctx->debug) {
                if (driver.cell->bel != BelId()) {
                    log_info("%s out bel:%s:%s\n", driver.cell->type.c_str(ctx),
                             ctx->getBelName(driver.cell->bel).str(ctx).c_str(), driver.port.c_str(ctx));
                } else {
                    log_info("%s out:%s:%s\n", driver.cell->type.c_str(ctx), ctx->nameOf(driver.cell),
                             driver.port.c_str(ctx));
                }
            }
            return true;
        }
    }
    return false;
}

// Segments
int GowinUtils::get_segments_count(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->segments.ssize();
}

void GowinUtils::get_segment_region(int s_i, int &seg_idx, int &x, int &min_x, int &min_y, int &max_x, int &max_y) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    seg_idx = extra->segments[s_i].seg_idx;
    x = extra->segments[s_i].x;
    min_x = extra->segments[s_i].min_x;
    min_y = extra->segments[s_i].min_y;
    max_x = extra->segments[s_i].max_x;
    max_y = extra->segments[s_i].max_y;
}

void GowinUtils::get_segment_wires_loc(int s_i, Loc &top, Loc &bottom) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    top.x = bottom.x = extra->segments[s_i].x;
    top.y = extra->segments[s_i].top_row;
    bottom.y = extra->segments[s_i].bottom_row;
}

void GowinUtils::get_segment_wires(int s_i, WireId &top, WireId &bottom) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    Loc top_loc, bottom_loc;
    get_segment_wires_loc(s_i, top_loc, bottom_loc);

    IdString tile = ctx->idf("X%dY%d", top_loc.x, top_loc.y);
    IdStringList name = IdStringList::concat(tile, IdString(extra->segments[s_i].top_wire));
    top = ctx->getWireByName(name);
    tile = ctx->idf("X%dY%d", bottom_loc.x, bottom_loc.y);
    name = IdStringList::concat(tile, IdString(extra->segments[s_i].bottom_wire));
    bottom = ctx->getWireByName(name);
}

void GowinUtils::get_segment_top_gate_wires(int s_i, WireId &wire0, WireId &wire1) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    Loc top_loc, bottom_loc;
    get_segment_wires_loc(s_i, top_loc, bottom_loc);

    IdString tile = ctx->idf("X%dY%d", top_loc.x, top_loc.y);
    IdStringList name;
    wire0 = WireId();
    IdString wire_name = IdString(extra->segments[s_i].top_gate_wire[0]);
    if (wire_name != IdString()) {
        name = IdStringList::concat(tile, wire_name);
        wire0 = ctx->getWireByName(name);
    }
    wire1 = WireId();
    wire_name = IdString(extra->segments[s_i].top_gate_wire[1]);
    if (wire_name != IdString()) {
        name = IdStringList::concat(tile, wire_name);
        wire1 = ctx->getWireByName(name);
    }
}

void GowinUtils::get_segment_bottom_gate_wires(int s_i, WireId &wire0, WireId &wire1) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    Loc top_loc, bottom_loc;
    get_segment_wires_loc(s_i, top_loc, bottom_loc);

    IdString tile = ctx->idf("X%dY%d", bottom_loc.x, bottom_loc.y);
    IdStringList name;
    wire0 = WireId();
    IdString wire_name = IdString(extra->segments[s_i].bottom_gate_wire[0]);
    if (wire_name != IdString()) {
        name = IdStringList::concat(tile, wire_name);
        wire0 = ctx->getWireByName(name);
    }
    wire1 = WireId();
    wire_name = IdString(extra->segments[s_i].bottom_gate_wire[1]);
    if (wire_name != IdString()) {
        name = IdStringList::concat(tile, wire_name);
        wire1 = ctx->getWireByName(name);
    }
}

// tile extra data
IdString GowinUtils::get_tile_class(int x, int y)
{
    int tile = tile_by_xy(ctx->chip_info, x, y);
    const Tile_extra_data_POD *extra =
            reinterpret_cast<const Tile_extra_data_POD *>(chip_tile_info(ctx->chip_info, tile).extra_data.get());
    return IdString(extra->class_id);
}

// oser16/ides16 aux cell offsets
Loc GowinUtils::get_tile_io16_offs(int x, int y)
{
    int tile = tile_by_xy(ctx->chip_info, x, y);
    const Tile_extra_data_POD *extra =
            reinterpret_cast<const Tile_extra_data_POD *>(chip_tile_info(ctx->chip_info, tile).extra_data.get());
    return Loc(extra->io16_x_off, extra->io16_y_off, 0);
}

// oser16/ides16 aux cell offsets
bool GowinUtils::get_i3c_capable(int x, int y)
{
    int tile = tile_by_xy(ctx->chip_info, x, y);
    const Tile_extra_data_POD *extra =
            reinterpret_cast<const Tile_extra_data_POD *>(chip_tile_info(ctx->chip_info, tile).extra_data.get());
    return extra->tile_flags & Tile_extra_data_POD::TILE_I3C_CAPABLE_IO;
}

// pin functions: GCLKT_4, SSPI_CS, READY etc
IdStringList GowinUtils::get_pin_funcs(BelId io_bel)
{
    IdStringList bel_name = ctx->getBelName(io_bel);

    const PadInfoPOD *pins = ctx->package_info->pads.get();
    size_t len = ctx->package_info->pads.ssize();
    for (size_t i = 0; i < len; i++) {
        const PadInfoPOD *pin = &pins[i];
        if (IdString(pin->tile) == bel_name[0] && IdString(pin->bel) == bel_name[1]) {
            return IdStringList::parse(ctx, IdString(pin->pad_function).str(ctx));
        }
    }
    return IdStringList();
}

// PLL pads
BelId GowinUtils::get_pll_bel(BelId io_bel, IdString type)
{
    IdStringList bel_name = ctx->getBelName(io_bel);

    const PadInfoPOD *pins = ctx->package_info->pads.get();
    size_t len = ctx->package_info->pads.ssize();
    for (size_t i = 0; i < len; i++) {
        const PadInfoPOD *pin = &pins[i];
        if (IdString(pin->tile) == bel_name[0] && IdString(pin->bel) == bel_name[1]) {
            const Pad_extra_data_POD *extra = reinterpret_cast<const Pad_extra_data_POD *>(pin->extra_data.get());
            if (extra != nullptr && IdString(extra->pll_type) == type) {
                return ctx->getBelByName(IdStringList::concat(IdString(extra->pll_tile), IdString(extra->pll_bel)));
            }
        }
    }
    return BelId();
}

BelId GowinUtils::get_dqce_bel(IdString spine_name)
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    for (auto &spine_bel : extra->dqce_bels) {
        if (IdString(spine_bel.spine) == spine_name) {
            return ctx->getBelByLocation(Loc(spine_bel.bel_x, spine_bel.bel_y, spine_bel.bel_z));
        }
    }
    return BelId();
}

BelId GowinUtils::get_dcs_bel(IdString spine_name)
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    for (auto &spine_bel : extra->dcs_bels) {
        if (IdString(spine_bel.spine) == spine_name) {
            return ctx->getBelByLocation(Loc(spine_bel.bel_x, spine_bel.bel_y, spine_bel.bel_z));
        }
    }
    return BelId();
}

BelId GowinUtils::get_dlldly_bel(BelId io_bel)
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    for (auto &io : extra->io_dlldly_bels) {
        if (IdStringList::parse(ctx, (IdString(io.io)).str(ctx)) == ctx->getBelName(io_bel)) {
            return ctx->getBelByName(IdStringList::parse(ctx, (IdString(io.dlldly)).str(ctx)));
        }
    }
    return BelId();
}

BelId GowinUtils::get_dhcen_bel(WireId hclkin_wire, IdString &side)
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    for (auto &wire_bel : extra->dhcen_bels) {
        IdString dst = IdString(wire_bel.pip_dst);
        IdString src = IdString(wire_bel.pip_src);
        IdStringList pip = IdStringList::concat(IdStringList::concat(IdString(wire_bel.pip_xy), dst), src);
        WireId wire = ctx->getPipDstWire(ctx->getPipByName(pip));
        if (wire == hclkin_wire) {
            side = IdString(wire_bel.side);
            return ctx->getBelByLocation(Loc(wire_bel.bel_x, wire_bel.bel_y, wire_bel.bel_z));
        }
    }
    return BelId();
}

IdString GowinUtils::get_dcs_prefix(void)
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return IdString(extra->dcs_prefix);
}

bool GowinUtils::is_simple_io_bel(BelId bel)
{
    return chip_bel_info(ctx->chip_info, bel).flags & BelFlags::FLAG_SIMPLE_IO;
}

Loc GowinUtils::get_pair_iologic_bel(Loc loc)
{
    int const z[] = {1, 0, 3, 2};
    loc.z = BelZ::IOLOGICA_Z + z[(loc.z - BelZ::IOLOGICA_Z)];
    return loc;
}

BelId GowinUtils::get_io_bel_from_iologic(BelId bel)
{
    Loc loc = ctx->getBelLocation(bel);
    loc.z = BelZ::IOBA_Z + ((loc.z - BelZ::IOLOGICA_Z) & 1);
    return ctx->getBelByLocation(loc);
}

bool GowinUtils::is_diff_io_supported(IdString type)
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    for (auto &dtype : extra->diff_io_types) {
        if (IdString(dtype) == type) {
            return true;
        }
    }
    return false;
}

bool GowinUtils::has_bottom_io_cnds(void)
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->bottom_io.conditions.size() != 0;
}

IdString GowinUtils::get_bottom_io_wire_a_net(int8_t condition)
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return IdString(extra->bottom_io.conditions[condition].wire_a_net);
}

IdString GowinUtils::get_bottom_io_wire_b_net(int8_t condition)
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return IdString(extra->bottom_io.conditions[condition].wire_b_net);
}

bool GowinUtils::has_BANDGAP(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::HAS_BANDGAP;
}

bool GowinUtils::has_PINCFG(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::HAS_PINCFG;
}

bool GowinUtils::need_CFGPINS_INVERSION(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::NEED_CFGPINS_INVERSION;
}

bool GowinUtils::has_I2CCFG(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::HAS_I2CCFG;
}

bool GowinUtils::has_DFF67(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::HAS_DFF67;
}

bool GowinUtils::has_spine_enable_nets(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->spine_select_wires_top.ssize() || extra->spine_select_wires_bottom.ssize();
}

bool GowinUtils::get_spine_select_wire(WireId spine, std::vector<std::pair<WireId, int>> &wires)
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());

    wires.clear();
    for (auto &rec : wire_in_bottom_half(spine) ? extra->spine_select_wires_bottom : extra->spine_select_wires_top) {
        if (IdString(rec.spine) == ctx->getWireName(spine)[1]) {
            IdString tile = ctx->idf("X%dY%d", rec.x, rec.y);
            IdStringList name = IdStringList::concat(tile, IdString(rec.wire));
            wires.push_back(std::make_pair(ctx->getWireByName(name), rec.vcc_gnd));
        }
    }
    return !wires.empty();
}

bool GowinUtils::has_CIN_MUX(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::HAS_CIN_MUX;
}

bool GowinUtils::has_SP32(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::HAS_SP32;
}

bool GowinUtils::need_SP_fix(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::NEED_SP_FIX;
}

bool GowinUtils::need_SDP_fix(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::NEED_SDP_FIX;
}

bool GowinUtils::need_BSRAM_OUTREG_fix(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::NEED_BSRAM_OUTREG_FIX;
}

bool GowinUtils::need_BSRAM_DP_CE_fix(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::NEED_BSRAM_DP_CE_FIX;
}

bool GowinUtils::need_BSRAM_RESET_fix(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::NEED_BSRAM_RESET_FIX;
}

bool GowinUtils::need_BLKSEL_fix(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::NEED_BLKSEL_FIX;
}

bool GowinUtils::has_PLL_HCLK(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::HAS_PLL_HCLK;
}

bool GowinUtils::has_CLKDIV_HCLK(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::HAS_CLKDIV_HCLK;
}

bool GowinUtils::has_5A_HCLK(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::HAS_5A_HCLK;
}

IdString GowinUtils::create_aux_name(IdString main_name, int idx, const char *str_suffix)
{
    return idx ? ctx->idf("%s%s%d", main_name.c_str(ctx), str_suffix, idx)
               : ctx->idf("%s%s", main_name.c_str(ctx), str_suffix);
}

std::unique_ptr<CellInfo> GowinUtils::create_cell(IdString name, IdString type)
{
    NPNR_ASSERT(!ctx->cells.count(name));
    return std::make_unique<CellInfo>(ctx, name, type);
}

// DSP
bool GowinUtils::has_5A_DSP(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->chip_flags & Extra_chip_data_POD::HAS_5A_DSP;
}

Loc GowinUtils::get_dsp_next_9_in_chain(Loc from) const
{
    Loc res;
    res.y = from.y;
    if (get_dsp_18_idx(from.z) == 0) {
        res.x = from.x;
        res.z = from.z + 4;
        return res;
    }
    if (get_dsp_macro_num(from.z)) {
        // next DSP
        res.x = from.x + 9;
        res.z = from.z & (~0x24);
    } else {
        // next macro
        res.x = from.x;
        res.z = get_dsp_next_macro(from.z) & (~4);
    }
    return res;
}

Loc GowinUtils::get_dsp_next_macro_in_chain(Loc from) const
{
    Loc res;
    res.y = from.y;
    if (get_dsp_macro_num(from.z)) {
        // next DSP
        res.x = from.x + 9;
        res.z = from.z & (~0x20);
    } else {
        // next macro
        res.x = from.x;
        res.z = get_dsp_next_macro(from.z);
    }
    return res;
}

Loc GowinUtils::get_dsp_next_in_chain_5a(Loc from) const
{
    Loc res;
    res.y = from.y;
    // next DSP
    int off = 3;
    if (from.y == get_center_row() && (from.x + 5) == get_center_col()) {
        off = 9;
    }
    res.x = from.x + off;
    res.z = from.z;
    return res;
}

Loc GowinUtils::get_dsp_next_in_chain(Loc from, IdString dsp_type) const
{
    if (dsp_type.in(id_PADD9, id_PADD18, id_MULT9X9, id_MULT18X18)) {
        return get_dsp_next_9_in_chain(from);
    }
    if (dsp_type.in(id_ALU54D, id_MULTALU18X18, id_MULTALU36X18, id_MULTADDALU18X18)) {
        return get_dsp_next_macro_in_chain(from);
    }
    if (dsp_type.in(id_MULTADDALU12X12, id_MULTALU27X18)) {
        return get_dsp_next_in_chain_5a(from);
    }
    NPNR_ASSERT_FALSE("Unknown DSP cell type.");
}

CellInfo *GowinUtils::dsp_bus_src(const CellInfo *ci, const char *bus_prefix, int wire_num) const
{
    bool connected_to_const = false; // and disconnected too
    CellInfo *connected_to_cell = nullptr;

    for (int i = 0; i < wire_num; ++i) {
        const NetInfo *net = ci->getPort(ctx->idf("%s%d", bus_prefix, i));
        if (connected_to_cell == nullptr) {
            if (net == nullptr || net->driver.cell == nullptr || net->name == ctx->id("$PACKER_VCC") ||
                net->name == ctx->id("$PACKER_GND")) {
                connected_to_const = true;
                continue;
            } else {
                if (connected_to_const) {
                    log_error("The %s cell %s bus is connected simultaneously to constants and to another DSP.\n",
                              ctx->nameOf(ci), bus_prefix);
                }
            }
        }
        if (net == nullptr || !is_dsp(net->driver.cell)) {
            log_error("The %s cell %s bus is not connected to another DSP.\n", ctx->nameOf(ci), bus_prefix);
        }
        if (connected_to_cell != nullptr && net->driver.cell != connected_to_cell) {
            log_error("The %s cell %s bus is connected to different DSPs: %s and %s.\n", ctx->nameOf(ci), bus_prefix,
                      ctx->nameOf(connected_to_cell), ctx->nameOf(net->driver.cell));
        }
        connected_to_cell = net->driver.cell;
    }
    if (connected_to_const) {
        return nullptr;
    }
    return connected_to_cell;
}

CellInfo *GowinUtils::dsp_bus_dst(const CellInfo *ci, const char *bus_prefix, int wire_num) const
{
    bool disconnected = false; // and disconnected too
    CellInfo *connected_to_cell = nullptr;

    for (int i = 0; i < wire_num; ++i) {
        const NetInfo *net = ci->getPort(ctx->idf("%s%d", bus_prefix, i));
        if (connected_to_cell == nullptr) {
            if (net == nullptr || net->users.entries() == 0) {
                disconnected = true;
                continue;
            } else {
                if (disconnected) {
                    log_error("The %s cell %s bus is partially disconnected.\n", ctx->nameOf(ci), bus_prefix);
                }
            }
        }
        if (net->users.entries() > 1) {
            log_error("Net %s has >1 users.\n", ctx->nameOf(net));
        }

        CellInfo *dst = (*net->users.begin()).cell;
        if (net == nullptr || !is_dsp(dst)) {
            log_error("The %s cell %s bus is not connected to another DSP.\n", ctx->nameOf(ci), bus_prefix);
        }
        if (connected_to_cell != nullptr && dst != connected_to_cell) {
            log_error("The %s cell %s bus is connected to different DSPs: %s and %s.\n", ctx->nameOf(ci), bus_prefix,
                      ctx->nameOf(connected_to_cell), ctx->nameOf(dst));
        }
        connected_to_cell = dst;
    }
    if (disconnected) {
        return nullptr;
    }
    return connected_to_cell;
}

int GowinUtils::get_hclk_for_io(Loc io_loc) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    for (auto &io_hclk : extra->io_to_hclk) {
        if (io_hclk.x == io_loc.x && io_hclk.y == io_loc.y) {
            return io_hclk.hclk_idx;
        }
    }
    return -1;
}

// Location of clkdiv2 for HCLK
void GowinUtils::get_clkdiv2_locs(int hclk_idx, std::vector<Loc> &locs)
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    locs.clear();
    for (auto &hclk_div2 : extra->hclk_div2) {
        if (hclk_div2.hclk_idx == hclk_idx) {
            locs.push_back(Loc(hclk_div2.x, hclk_div2.y, hclk_div2.z));
        }
    }
}

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

// Get spec locations
int GowinUtils::get_center_row(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->center_row;
}
int GowinUtils::get_center_col(void) const
{
    const Extra_chip_data_POD *extra = reinterpret_cast<const Extra_chip_data_POD *>(ctx->chip_info->extra_data.get());
    return extra->center_col;
}

NEXTPNR_NAMESPACE_END
