#include "design_utils.h"
#include "log.h"
#include "nextpnr.h"

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

#include "gowin.h"
#include "gowin_utils.h"
#include "pack.h"

#include <cinttypes>

NEXTPNR_NAMESPACE_BEGIN

// ===================================
// Block RAM
// ===================================
// Memory is always 36-bit, consisting of 9-bit bytes; 8-bit primitives are
// formed by “skipping” every 9th bit on the DI and DO buses. Here, the
// skipping works as follows: if the primitive is 8-bit (16 or 32), then every
// 9th bit becomes the 10th bit and so on.
void GowinPacker::bsram_rename_ports(CellInfo *ci, int bit_width, char const *from, char const *to, int offset)
{
    int num = (bit_width == 9 || bit_width == 18 || bit_width == 36) ? 36 : 32;
    for (int i = 0, j = offset; i < num; ++i, ++j) {
        if (i && (i % 8) == 0 && (bit_width == 16 || bit_width == 32)) {
            ++j;
        }
        ci->renamePort(ctx->idf(from, i), ctx->idf(to, offset ? j % 36 : j));
    }
}

// We solve the BLKSEL problems that are observed on some chips by
// connecting the BLKSEL ports to constant networks so that this BSRAM will
// be selected, the actual selection is made by manipulating the Clock
// Enable pin using a LUT-based decoder.
void GowinPacker::bsram_fix_blksel(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
    // is BSRAM enabled
    NetInfo *ce_net = ci->getPort(id_CE);
    if (ce_net == nullptr || ce_net->name == ctx->id("$PACKER_GND")) {
        return;
    }

    // port name, BLK_SEL parameter for this port
    std::vector<std::pair<IdString, int>> dyn_blksel;

    int blk_sel_parameter = ci->params.at(id_BLK_SEL).as_int64();
    for (int i = 0; i < 3; ++i) {
        IdString pin_name = ctx->idf("BLKSEL[%d]", i);
        NetInfo *net = ci->getPort(pin_name);
        if (net == nullptr || net->name == ctx->id("$PACKER_GND") || net->name == ctx->id("$PACKER_VCC")) {
            continue;
        }
        dyn_blksel.push_back(std::make_pair(pin_name, (blk_sel_parameter >> i) & 1));
    }

    if (dyn_blksel.empty()) {
        return;
    }

    if (ctx->verbose) {
        log_info("  apply the BSRAM BLKSEL fix\n");
    }

    // Make a decoder
    auto lut_cell = gwu.create_cell(gwu.create_aux_name(ci->name, 0, "_blksel_lut$"), id_LUT4);
    CellInfo *lut = lut_cell.get();
    lut->addInput(id_I3);
    ci->movePortTo(id_CE, lut, id_I3);
    lut->addOutput(id_F);
    ci->connectPorts(id_CE, lut, id_F);

    NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
    NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();

    // Connected CE to I3 to make it easy to calculate the decoder
    int init = 0x100; // CE == 0 -->  F = 0
                      // CE == 1 -->  F = decoder result
    int idx = 0;
    for (auto &port : dyn_blksel) {
        IdString lut_input_name = ctx->idf("I%d", idx);
        ci->movePortTo(port.first, lut, lut_input_name);
        if (port.second) {
            init <<= (1 << idx);
            ci->connectPort(port.first, vcc_net);
        } else {
            ci->connectPort(port.first, vss_net);
        }
        ++idx;
    }
    lut->setParam(id_INIT, init);

    new_cells.push_back(std::move(lut_cell));
}

// Some chips cannot, for some reason, use internal BSRAM registers to
// implement READ_MODE=1'b1 (pipeline) with a word width other than 32 or
// 36 bits.
// We work around this by adding an external DFF and using BSRAM
// as READ_MODE=1'b0 (bypass).
void GowinPacker::bsram_fix_outreg(CellInfo *ci, int bit_width, IdString ce_pin, IdString oce_pin, IdString clk_pin,
                                   IdString reset_pin, IdString do_pin, IdString read_mode_param,
                                   std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
    if (ci->params.at(read_mode_param).as_int64() == 0) {
        return;
    }
    NetInfo *ce_net = ci->getPort(ce_pin);
    NetInfo *oce_net = ci->getPort(oce_pin);
    if (ce_net == nullptr || oce_net == nullptr) {
        return;
    }
    if (ce_net->name == ctx->id("$PACKER_GND") || oce_net->name == ctx->id("$PACKER_GND")) {
        return;
    }

    if (ctx->verbose) {
        log_info("  apply the BSRAM OUTREG fix\n");
    }
    ci->setParam(read_mode_param, 0);
    ci->disconnectPort(oce_pin);
    ci->connectPort(oce_pin, ce_net);

    NetInfo *reset_net = ci->getPort(reset_pin);
    bool sync_reset = ci->params.at(id_RESET_MODE).as_string() == std::string("SYNC");
    IdString dff_type = sync_reset ? id_DFFRE : id_DFFCE;
    IdString reset_port = sync_reset ? id_RESET : id_CLEAR;

    for (int i = 0; i < bit_width; ++i) {
        IdString do_name = ctx->idf("%s[%d]", do_pin.c_str(ctx), i);
        const NetInfo *net = ci->getPort(do_name);
        if (net != nullptr) {
            if (net->users.empty()) {
                ci->disconnectPort(do_name);
                continue;
            }

            // create DFF
            auto cache_dff_cell = gwu.create_cell(gwu.create_aux_name(ci->name, i, "_cache_dff$"), dff_type);
            CellInfo *cache_dff = cache_dff_cell.get();
            cache_dff->addInput(id_CE);
            cache_dff->connectPort(id_CE, oce_net);

            cache_dff->addInput(reset_port);
            cache_dff->connectPort(reset_port, reset_net);

            ci->copyPortTo(clk_pin, cache_dff, id_CLK);

            cache_dff->addOutput(id_Q);
            ci->movePortTo(do_name, cache_dff, id_Q);

            cache_dff->addInput(id_D);
            ci->connectPorts(do_name, cache_dff, id_D);

            new_cells.push_back(std::move(cache_dff_cell));
        }
    }
}

// Analysis of the images generated by the IDE showed that some components
// are being added at the input and output of the BSRAM.  Two LUTs are
// added on the WRE and CE inputs (strangely, OCE is not affected), a pair
// of LUT-DFFs on each DO output, and one or two flipflops of different
// types in the auxiliary network.
// The semantics of these additions are unclear, but we can replicate this behavior.
//  Fix BSRAM in single port mode.
void GowinPacker::bsram_fix_sp(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
    int bit_width = ci->params.at(id_BIT_WIDTH).as_int64();

    if (ctx->verbose) {
        log_info("  apply the SP fix\n");
    }
    // create WRE LUT
    auto wre_lut_cell = gwu.create_cell(gwu.create_aux_name(ci->name, 0, "_wre_lut$"), id_LUT4);
    CellInfo *wre_lut = wre_lut_cell.get();
    wre_lut->setParam(id_INIT, 0x8888);
    ci->movePortTo(id_CE, wre_lut, id_I0);
    ci->movePortTo(id_WRE, wre_lut, id_I1);
    wre_lut->addOutput(id_F);
    ci->connectPorts(id_WRE, wre_lut, id_F);

    // create CE LUT
    auto ce_lut_cell = gwu.create_cell(gwu.create_aux_name(ci->name, 0, "_ce_lut$"), id_LUT4);
    CellInfo *ce_lut = ce_lut_cell.get();
    ce_lut->setParam(id_INIT, 0xeeee);
    wre_lut->copyPortTo(id_I0, ce_lut, id_I0);
    wre_lut->copyPortTo(id_I1, ce_lut, id_I1);
    ce_lut->addOutput(id_F);
    ci->connectPorts(id_CE, ce_lut, id_F);

    // create ce reg
    int write_mode = ci->params.at(id_WRITE_MODE).as_int64();
    IdString dff_type = write_mode ? id_DFF : id_DFFR;
    auto ce_pre_dff_cell = gwu.create_cell(gwu.create_aux_name(ci->name, 0, "_ce_pre_dff$"), dff_type);
    CellInfo *ce_pre_dff = ce_pre_dff_cell.get();
    ce_pre_dff->addInput(id_D);
    ce_lut->copyPortTo(id_I0, ce_pre_dff, id_D);
    ci->copyPortTo(id_CLK, ce_pre_dff, id_CLK);
    if (dff_type == id_DFFR) {
        wre_lut->copyPortTo(id_I1, ce_pre_dff, id_RESET);
    }
    ce_pre_dff->addOutput(id_Q);

    // new ce src with Q pin (used by output pins, not by BSRAM itself)
    CellInfo *new_ce_net_src = ce_pre_dff;

    // add delay register in pipeline mode
    int read_mode = ci->params.at(id_READ_MODE).as_int64();
    if (read_mode) {
        auto ce_pipe_dff_cell = gwu.create_cell(gwu.create_aux_name(ci->name, 0, "_ce_pipe_dff$"), id_DFF);
        new_cells.push_back(std::move(ce_pipe_dff_cell));
        CellInfo *ce_pipe_dff = new_cells.back().get();
        ce_pipe_dff->addInput(id_D);
        new_ce_net_src->connectPorts(id_Q, ce_pipe_dff, id_D);
        ci->copyPortTo(id_CLK, ce_pipe_dff, id_CLK);
        ce_pipe_dff->addOutput(id_Q);
        new_ce_net_src = ce_pipe_dff;
    }

    // used outputs of the BSRAM convert to cached
    for (int i = 0; i < bit_width; ++i) {
        IdString do_name = ctx->idf("DO[%d]", i);
        const NetInfo *net = ci->getPort(do_name);
        if (net != nullptr) {
            if (net->users.empty()) {
                ci->disconnectPort(do_name);
                continue;
            }
            // create cache lut
            auto cache_lut_cell = gwu.create_cell(gwu.create_aux_name(ci->name, i, "_cache_lut$"), id_LUT4);
            CellInfo *cache_lut = cache_lut_cell.get();
            cache_lut->setParam(id_INIT, 0xcaca);
            cache_lut->addInput(id_I0);
            cache_lut->addInput(id_I1);
            cache_lut->addInput(id_I2);
            ci->movePortTo(do_name, cache_lut, id_F);
            ci->connectPorts(do_name, cache_lut, id_I1);
            new_ce_net_src->connectPorts(id_Q, cache_lut, id_I2);

            // create cache DFF
            auto cache_dff_cell = gwu.create_cell(gwu.create_aux_name(ci->name, i, "_cache_dff$"), id_DFFE);
            CellInfo *cache_dff = cache_dff_cell.get();
            cache_dff->addInput(id_CE);
            cache_dff->addInput(id_D);
            ci->copyPortTo(id_CLK, cache_dff, id_CLK);
            new_ce_net_src->connectPorts(id_Q, cache_dff, id_CE);
            cache_lut->copyPortTo(id_I1, cache_dff, id_D);
            cache_dff->addOutput(id_Q);
            cache_dff->connectPorts(id_Q, cache_lut, id_I0);

            new_cells.push_back(std::move(cache_lut_cell));
            new_cells.push_back(std::move(cache_dff_cell));
        }
    }

    new_cells.push_back(std::move(wre_lut_cell));
    new_cells.push_back(std::move(ce_lut_cell));
    new_cells.push_back(std::move(ce_pre_dff_cell));
}

void GowinPacker::pack_ROM(CellInfo *ci)
{
    int default_bw = 32;
    // XXX use block 111
    ci->setParam(ctx->id("BLK_SEL"), Property(7, 32));
    if (ci->type == id_pROM) {
        ci->setAttr(id_BSRAM_SUBTYPE, Property(""));
    } else {
        ci->setAttr(id_BSRAM_SUBTYPE, Property("X9"));
        default_bw = 36;
    }

    NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
    NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();
    for (int i = 0; i < 3; ++i) {
        IdString port = ctx->idf("BLKSEL%d", i);
        ci->addInput(port);
        ci->connectPort(port, vcc_net);
        port = ctx->idf("BLKSELB%d", i);
        ci->addInput(port);
        ci->connectPort(port, vcc_net);
    }

    ci->addInput(id_WRE);
    ci->connectPort(id_WRE, vss_net);
    ci->addInput(id_WREB);
    ci->connectPort(id_WREB, vss_net);

    if (!ci->params.count(id_BIT_WIDTH)) {
        ci->setParam(id_BIT_WIDTH, Property(default_bw, 32));
    }

    int bit_width = ci->params.at(id_BIT_WIDTH).as_int64();
    if (bit_width == 32 || bit_width == 36) {
        ci->copyPortTo(id_CLK, ci, id_CLKB);
        ci->copyPortTo(id_CE, ci, id_CEB);
        ci->copyPortTo(id_OCE, ci, id_OCEB);
        ci->copyPortTo(id_RESET, ci, id_RESETB);

        for (int i = 0; i < 14; ++i) {
            ci->renamePort(ctx->idf("AD[%d]", i), ctx->idf("ADA%d", i));
            ci->copyPortTo(ctx->idf("ADA%d", i), ci, ctx->idf("ADB%d", i));
        }
        bsram_rename_ports(ci, bit_width, "DO[%d]", "DO%d");
    } else {
        // use port B
        ci->renamePort(id_CLK, id_CLKB);
        ci->renamePort(id_OCE, id_OCEB);
        ci->renamePort(id_CE, id_CEB);
        ci->renamePort(id_RESET, id_RESETB);

        ci->addInput(id_CEA);
        ci->connectPort(id_CEA, vss_net);
        for (int i = 0; i < 14; ++i) {
            ci->renamePort(ctx->idf("AD[%d]", i), ctx->idf("ADB%d", i));
        }
        bsram_rename_ports(ci, bit_width, "DO[%d]", "DO%d", 18);
    }
}

void GowinPacker::divide_sdp(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
    if (ctx->verbose) {
        log_info("  divide SDP\n");
    }

    int bw = ci->params.at(id_BIT_WIDTH_0).as_int64();
    NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
    NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();

    IdString cell_type = bw == 32 ? id_SDPB : id_SDPX9B;
    IdString name = ctx->idf("%s_AUX", ctx->nameOf(ci));

    auto sdp_cell = gwu.create_cell(name, cell_type);
    CellInfo *sdp = sdp_cell.get();
    sdp->setAttr(id_AUX, 1);

    int new_bw = bw / 2;
    ci->setParam(id_BIT_WIDTH_0, new_bw);
    ci->setParam(id_BIT_WIDTH_1, new_bw);
    sdp->params = ci->params;
    sdp->setParam(id_BIT_WIDTH_0, new_bw);
    sdp->setParam(id_BIT_WIDTH_1, new_bw);

    // copy control ports
    ci->copyPortBusTo(ctx->id("BLKSELA"), 0, true, sdp, ctx->id("BLKSELA"), 0, true, 3);
    ci->copyPortBusTo(ctx->id("BLKSELB"), 0, true, sdp, ctx->id("BLKSELB"), 0, true, 3);
    ci->copyPortTo(id_CEA, sdp, id_CEA);
    ci->copyPortTo(id_CEB, sdp, id_CEB);
    ci->copyPortTo(id_CLKA, sdp, id_CLKA);
    ci->copyPortTo(id_CLKB, sdp, id_CLKB);
    ci->copyPortTo(id_OCE, sdp, id_OCE);
    ci->copyPortTo(id_RESET, sdp, id_RESET);

    //  Separate port A
    ci->movePortTo(ctx->id("ADA[2]"), sdp, ctx->id("ADA[0]"));
    ci->movePortTo(ctx->id("ADA[3]"), sdp, ctx->id("ADA[1]"));

    ci->addInput(ctx->id("ADA[2]"));
    ci->addInput(ctx->id("ADA[3]"));
    ci->connectPort(ctx->id("ADA[2]"), vss_net);
    ci->connectPort(ctx->id("ADA[3]"), vss_net);

    sdp->addInput(ctx->id("ADA[2]"));
    sdp->addInput(ctx->id("ADA[3]"));
    sdp->connectPort(ctx->id("ADA[2]"), vss_net);
    sdp->connectPort(ctx->id("ADA[3]"), vss_net);

    ci->disconnectPort(ctx->id("ADA[4]"));
    ci->connectPort(ctx->id("ADA[4]"), vss_net);
    sdp->addInput(ctx->id("ADA[4]"));
    sdp->connectPort(ctx->id("ADA[4]"), vcc_net);

    ci->copyPortBusTo(id_ADA, 5, true, sdp, id_ADA, 5, true, 9);

    //  Separate port B
    for (int i = 0; i < 4; ++i) {
        IdString port = ctx->idf("ADB[%d]", i);
        ci->disconnectPort(port);
        ci->connectPort(port, vss_net);
        ci->copyPortTo(port, sdp, port);
    }

    ci->disconnectPort(ctx->id("ADB[4]"));
    ci->connectPort(ctx->id("ADB[4]"), vss_net);
    sdp->addInput(ctx->id("ADB[4]"));
    sdp->connectPort(ctx->id("ADB[4]"), vcc_net);

    ci->copyPortBusTo(id_ADB, 5, true, sdp, id_ADB, 5, true, 9);

    ci->movePortBusTo(id_DI, new_bw, true, sdp, id_DI, 0, true, new_bw);
    ci->movePortBusTo(id_DO, new_bw, true, sdp, id_DO, 0, true, new_bw);

    new_cells.push_back(std::move(sdp_cell));
}

void GowinPacker::pack_SDPB(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
    int default_bw = 32;
    if (ci->type == id_SDPB) {
        ci->setAttr(id_BSRAM_SUBTYPE, Property(""));
    } else {
        ci->setAttr(id_BSRAM_SUBTYPE, Property("X9"));
        default_bw = 36;
    }

    if (!ci->params.count(id_BIT_WIDTH_0)) {
        ci->setParam(id_BIT_WIDTH_0, Property(default_bw, 32));
    }
    if (!ci->params.count(id_BIT_WIDTH_1)) {
        ci->setParam(id_BIT_WIDTH_1, Property(default_bw, 32));
    }

    int bit_width = ci->params.at(id_BIT_WIDTH_0).as_int64();

    if ((bit_width == 32 || bit_width == 36) && gwu.need_SDP_fix()) {
        int bit_width_b = ci->params.at(id_BIT_WIDTH_1).as_int64();
        if (bit_width == bit_width_b) {
            divide_sdp(ci, new_cells);
        } else {
            log_error("The fix for SDP when ports A and B have different bit widths has not yet been implemented. "
                      "Cell: '%s'\n",
                      ci->type.c_str(ctx));
        }
    }

    NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
    NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();

    ci->copyPortTo(id_OCE, ci, id_OCEB);

    // If misconnected RESET
    if (gwu.need_BSRAM_RESET_fix()) {
        ci->renamePort(id_RESET, id_RESETB);
    }

    // Port A
    ci->addInput(id_WREA);
    ci->connectPort(id_WREA, vcc_net);

    // Port B
    ci->addInput(id_WREB);
    bit_width = ci->params.at(id_BIT_WIDTH_1).as_int64();

    if (bit_width == 32 || bit_width == 36) {
        ci->connectPort(id_WREB, vcc_net);
        bsram_rename_ports(ci, bit_width, "DO[%d]", "DO%d");
    } else {
        ci->connectPort(id_WREB, vss_net);
        bsram_rename_ports(ci, bit_width, "DO[%d]", "DO%d", 18);
    }
    bsram_rename_ports(ci, bit_width, "DI[%d]", "DI%d");
    gwu.remove_brackets(ci);
}

void GowinPacker::pack_DPB(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
    int default_bw = 16;
    if (ci->type == id_DPB) {
        ci->setAttr(id_BSRAM_SUBTYPE, Property(""));
    } else {
        ci->setAttr(id_BSRAM_SUBTYPE, Property("X9"));
        default_bw = 18;
    }

    if (!ci->params.count(id_BIT_WIDTH_0)) {
        ci->setParam(id_BIT_WIDTH_0, Property(default_bw, 32));
    }
    int bit_width = ci->params.at(id_BIT_WIDTH_0).as_int64();
    bsram_rename_ports(ci, bit_width, "DIA[%d]", "DIA%d");

    if (bit_width < 16 && gwu.need_BSRAM_OUTREG_fix()) {
        bsram_fix_outreg(ci, bit_width, id_CEA, id_OCEA, id_CLKA, id_RESETA, id_DOA, id_READ_MODE0, new_cells);
    }
    bsram_rename_ports(ci, bit_width, "DOA[%d]", "DOA%d");
    // In BYPASS mode, the OCE signal is dictated by CE.
    if (gwu.need_BSRAM_DP_CE_fix()) {
        if (bit_width <= 9) {
            ci->disconnectPort(id_OCEA);
            ci->copyPortTo(id_CEA, ci, id_OCEA);
        }
    }
    if (!ci->params.count(id_BIT_WIDTH_1)) {
        ci->setParam(id_BIT_WIDTH_1, Property(default_bw, 32));
    }
    bit_width = ci->params.at(id_BIT_WIDTH_1).as_int64();
    bsram_rename_ports(ci, bit_width, "DIB[%d]", "DIB%d");

    if (bit_width < 16 && gwu.need_BSRAM_OUTREG_fix()) {
        bsram_fix_outreg(ci, bit_width, id_CEB, id_OCEB, id_CLKB, id_RESETB, id_DOB, id_READ_MODE1, new_cells);
    }
    bsram_rename_ports(ci, bit_width, "DOB[%d]", "DOB%d");

    // In BYPASS mode, the OCE signal is dictated by CE.
    if (gwu.need_BSRAM_DP_CE_fix()) {
        if (bit_width <= 9) {
            ci->disconnectPort(id_OCEB);
            ci->copyPortTo(id_CEB, ci, id_OCEB);
        }
    }

    gwu.remove_brackets(ci);
}

void GowinPacker::divide_sp(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
    if (ctx->verbose) {
        log_info("  divide SP\n");
    }

    int bw = ci->params.at(id_BIT_WIDTH).as_int64();
    NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
    NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();

    IdString cell_type = bw == 32 ? id_SP : id_SPX9;
    IdString name = ctx->idf("%s_AUX", ctx->nameOf(ci));

    auto sp_cell = gwu.create_cell(name, cell_type);
    CellInfo *sp = sp_cell.get();
    sp->setAttr(id_AUX, 1);

    ci->copyPortTo(id_CLK, sp, id_CLK);
    ci->copyPortTo(id_OCE, sp, id_OCE);
    ci->copyPortTo(id_CE, sp, id_CE);
    ci->copyPortTo(id_RESET, sp, id_RESET);
    ci->copyPortTo(id_WRE, sp, id_WRE);

    // XXX Separate "byte enable" port
    ci->movePortTo(ctx->id("AD[2]"), sp, ctx->id("AD[0]"));
    ci->movePortTo(ctx->id("AD[3]"), sp, ctx->id("AD[1]"));
    ci->connectPort(ctx->id("AD[2]"), vss_net);
    ci->connectPort(ctx->id("AD[3]"), vss_net);

    sp->addInput(ctx->id("AD[2]"));
    sp->connectPort(ctx->id("AD[2]"), vss_net);
    sp->addInput(ctx->id("AD[3]"));
    sp->connectPort(ctx->id("AD[3]"), vss_net);

    ci->disconnectPort(ctx->id("AD[4]"));
    ci->connectPort(ctx->id("AD[4]"), vss_net);
    sp->addInput(ctx->id("AD[4]"));
    sp->connectPort(ctx->id("AD[4]"), vcc_net);

    ci->copyPortBusTo(id_AD, 5, true, sp, id_AD, 5, true, 9);

    sp->params = ci->params;

    bw /= 2;
    ci->setParam(id_BIT_WIDTH, Property(bw, 32));
    sp->setParam(id_BIT_WIDTH, Property(bw, 32));
    ci->movePortBusTo(id_DI, bw, true, sp, id_DI, 0, true, bw);
    ci->movePortBusTo(id_DO, bw, true, sp, id_DO, 0, true, bw);

    ci->copyPortBusTo(ctx->id("BLKSEL"), 0, true, sp, ctx->id("BLKSEL"), 0, true, 3);

    new_cells.push_back(std::move(sp_cell));
}

void GowinPacker::pack_SP(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
    int default_bw = 32;
    if (ci->type == id_SP) {
        ci->setAttr(id_BSRAM_SUBTYPE, Property(""));
    } else {
        ci->setAttr(id_BSRAM_SUBTYPE, Property("X9"));
        default_bw = 36;
    }
    if (!ci->params.count(id_BIT_WIDTH)) {
        ci->setParam(id_BIT_WIDTH, Property(default_bw, 32));
    }

    int bit_width = ci->params.at(id_BIT_WIDTH).as_int64();

    if (!ci->attrs.count(id_AUX)) {
        // XXX strange WRE<->CE relations
        // Gowin IDE adds two LUTs to the WRE and CE signals. The logic is
        // unclear, but without them effects occur. Perhaps this is a
        // correction of some BSRAM defects.
        if (gwu.need_SP_fix()) {
            bsram_fix_sp(ci, new_cells);
        }

        // Some chips have problems with BLKSEL ports
        if (gwu.need_BLKSEL_fix()) {
            bsram_fix_blksel(ci, new_cells);
        }

        // Some chips have faulty output registers
        if (bit_width < 32 && gwu.need_BSRAM_OUTREG_fix()) {
            bsram_fix_outreg(ci, bit_width, id_CE, id_OCE, id_CLK, id_RESET, id_DO, id_READ_MODE, new_cells);
        }
    }

    // XXX UG285-1.3.6_E Gowin BSRAM & SSRAM User Guide:
    // For GW1N-9/GW1NR-9/GW1NS-4 series, 32/36-bit SP/SPX9 is divided into two
    // SP/SPX9s, which occupy two BSRAMs.
    // So divide it here
    if ((bit_width == 32 || bit_width == 36) && !gwu.has_SP32()) {
        divide_sp(ci, new_cells);
        bit_width = ci->params.at(id_BIT_WIDTH).as_int64();
    }

    NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
    NetInfo *gnd_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();
    for (int i = 0; i < 3; ++i) {
        ci->renamePort(ctx->idf("BLKSEL[%d]", i), ctx->idf("BLKSEL%d", i));
        if (bit_width == 32 || bit_width == 36) {
            ci->copyPortTo(ctx->idf("BLKSEL%d", i), ci, ctx->idf("BLKSELB%d", i));
        }
    }

    for (int i = 0; i < 14; ++i) {
        ci->renamePort(ctx->idf("AD[%d]", i), ctx->idf("AD%d", i));
        if (bit_width == 32 || bit_width == 36) {
            // Since we are dividing 32/36 bits into two parts between
            // ports A and B, the ‘Byte Enables’ require special
            // separation.
            if (i < 4) {
                if (i > 1) {
                    ci->movePortTo(ctx->idf("AD%d", i), ci, ctx->idf("ADB%d", i - 2));
                    ci->connectPort(ctx->idf("AD%d", i), gnd_net);
                    ci->addInput(ctx->idf("ADB%d", i));
                    ci->connectPort(ctx->idf("ADB%d", i), gnd_net);
                }
            } else {
                ci->copyPortTo(ctx->idf("AD%d", i), ci, ctx->idf("ADB%d", i));
            }
        }
    }
    if (bit_width == 32 || bit_width == 36) {
        ci->copyPortTo(id_CLK, ci, id_CLKB);
        ci->copyPortTo(id_OCE, ci, id_OCEB);
        ci->copyPortTo(id_CE, ci, id_CEB);
        ci->copyPortTo(id_RESET, ci, id_RESETB);
        ci->copyPortTo(id_WRE, ci, id_WREB);
        ci->disconnectPort(ctx->id("AD4"));
        ci->connectPort(ctx->id("AD4"), gnd_net);
        ci->disconnectPort(ctx->id("ADB4"));
        ci->connectPort(ctx->id("ADB4"), vcc_net);
    }
    bsram_rename_ports(ci, bit_width, "DI[%d]", "DI%d");
    bsram_rename_ports(ci, bit_width, "DO[%d]", "DO%d");
}

void GowinPacker::pack_bsram(void)
{
    std::vector<std::unique_ptr<CellInfo>> new_cells;
    log_info("Pack BSRAMs...\n");

    auto do_bsram = [&](CellInfo *ci) {
        if (ctx->verbose) {
            log_info(" pack %s\n", ci->type.c_str(ctx));
        }
        switch (ci->type.hash()) {
        case ID_pROMX9: /* fallthrough */
        case ID_pROM:
            pack_ROM(ci);
            ci->type = id_ROM;
            break;
        case ID_SDPX9B: /* fallthrough */
        case ID_SDPB:
            pack_SDPB(ci, new_cells);
            ci->type = id_SDP;
            break;
        case ID_DPX9B: /* fallthrough */
        case ID_DPB:
            pack_DPB(ci, new_cells);
            ci->type = id_DP;
            break;
        case ID_SPX9: /* fallthrough */
        case ID_SP:
            pack_SP(ci, new_cells);
            ci->type = id_SP;
            break;
        default:
            log_error("Unsupported BSRAM type '%s'\n", ci->type.c_str(ctx));
        }
    };

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

    // Process new cells. New cells should not generate more.
    for (auto &cell : new_cells) {
        auto ci = cell.get();
        if (is_bsram(ci)) {
            do_bsram(ci);
        }
        ctx->cells[cell->name] = std::move(cell);
    }
}
NEXTPNR_NAMESPACE_END
