#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

// ===================================
// DSP
// ===================================
void GowinPacker::pass_net_type(CellInfo *ci, IdString port)
{
    const NetInfo *net = ci->getPort(port);
    std::string connected_net = "NET";
    if (net != nullptr) {
        if (net->name == ctx->id("$PACKER_VCC")) {
            connected_net = "VCC";
        } else if (net->name == ctx->id("$PACKER_GND")) {
            connected_net = "GND";
        }
        ci->setAttr(ctx->idf("NET_%s", port.c_str(ctx)), connected_net);
    } else {
        ci->setAttr(ctx->idf("NET_%s", port.c_str(ctx)), std::string(""));
    }
}

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

    std::vector<std::unique_ptr<CellInfo>> new_dsps;
    std::vector<CellInfo *> dsp_heads;
    std::vector<IdString> cells_to_remove;

    auto process_dsp_cell = [&](CellInfo *ci) {
        if (ctx->verbose) {
            log_info(" pack %s %s\n", ci->type.c_str(ctx), ctx->nameOf(ci));
        }
        switch (ci->type.hash()) {
        case ID_PADD9: {
            gwu.remove_brackets(ci);
            pass_net_type(ci, id_ASEL);

            // ADD_SUB wire
            IdString add_sub_net = ctx->id("$PACKER_GND");
            if (ci->params.count(ctx->id("ADD_SUB"))) {
                if (ci->params.at(ctx->id("ADD_SUB")).as_int64() == 1) {
                    add_sub_net = ctx->id("$PACKER_VCC");
                }
            }
            ci->addInput(ctx->id("ADDSUB"));
            ci->connectPort(ctx->id("ADDSUB"), ctx->nets.at(add_sub_net).get());

            // PADD does not have outputs to the outside of the DSP -
            // it is always connected to the inputs of the multiplier;
            // to emulate a separate PADD primitive, we use
            // multiplication by input C, equal to 1. We can switch the
            // multiplier to multiplication mode by C in gowin_pack,
            // but we will have to generate the value 1 at input C
            // here.
            ci->addInput(ctx->id("C0"));
            ci->connectPort(ctx->id("C0"), ctx->nets.at(ctx->id("$PACKER_VCC")).get());
            for (int i = 1; i < 9; ++i) {
                ci->addInput(ctx->idf("C%d", i));
                ci->connectPort(ctx->idf("C%d", i), ctx->nets.at(ctx->id("$PACKER_GND")).get());
            }
            // mark mult9x9 as used by making cluster
            ci->cluster = ci->name;
            ci->constr_abs_z = false;
            ci->constr_x = 0;
            ci->constr_y = 0;
            ci->constr_y = 0;

            IdString mult_name = gwu.create_aux_name(ci->name);
            std::unique_ptr<CellInfo> mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL);
            new_cells.push_back(std::move(mult_cell));
            CellInfo *mult_ci = new_cells.back().get();

            mult_ci->cluster = ci->name;
            mult_ci->constr_x = 0;
            mult_ci->constr_y = 0;
            mult_ci->constr_z = gwu.get_dsp_mult_from_padd(0);

            // DSP head?
            if (gwu.dsp_bus_src(ci, "SI", 9) == nullptr && gwu.dsp_bus_dst(ci, "SBO", 9) == nullptr) {
                for (int i = 0; i < 9; ++i) {
                    ci->disconnectPort(ctx->idf("SI%d", i));
                    ci->disconnectPort(ctx->idf("SBO%d", i));
                }
                dsp_heads.push_back(ci);
                if (ctx->verbose) {
                    log_info(" found a DSP head: %s\n", ctx->nameOf(ci));
                }
            }
        } break;
        case ID_PADD18: {
            pass_net_type(ci, id_ASEL);
            gwu.remove_brackets(ci);

            // ADD_SUB wire
            IdString add_sub_net = ctx->id("$PACKER_GND");
            if (ci->params.count(ctx->id("ADD_SUB"))) {
                if (ci->params.at(ctx->id("ADD_SUB")).as_int64() == 1) {
                    add_sub_net = ctx->id("$PACKER_VCC");
                }
            }
            ci->addInput(ctx->id("ADDSUB"));
            ci->connectPort(ctx->id("ADDSUB"), ctx->nets.at(add_sub_net).get());

            // XXX form C as 1
            ci->addInput(ctx->id("C0"));
            ci->connectPort(ctx->id("C0"), ctx->nets.at(ctx->id("$PACKER_VCC")).get());
            for (int i = 1; i < 18; ++i) {
                ci->addInput(ctx->idf("C%d", i));
                ci->connectPort(ctx->idf("C%d", i), ctx->nets.at(ctx->id("$PACKER_GND")).get());
            }
            //
            // add padd9s and mult9s as a children
            ci->cluster = ci->name;
            ci->constr_abs_z = false;
            ci->constr_x = 0;
            ci->constr_y = 0;
            ci->constr_z = 0;
            ci->constr_children.clear();

            for (int i = 0; i < 2; ++i) {
                IdString padd_name = gwu.create_aux_name(ci->name, i * 2);
                std::unique_ptr<CellInfo> padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(padd_cell));
                CellInfo *padd_ci = new_cells.back().get();

                padd_ci->cluster = ci->name;
                padd_ci->constr_abs_z = false;
                padd_ci->constr_x = 0;
                padd_ci->constr_y = 0;
                padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::PADD18_0_0_Z + i;

                IdString mult_name = gwu.create_aux_name(ci->name, i * 2 + 1);
                std::unique_ptr<CellInfo> mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(mult_cell));
                CellInfo *mult_ci = new_cells.back().get();

                mult_ci->cluster = ci->name;
                mult_ci->constr_abs_z = false;
                mult_ci->constr_x = 0;
                mult_ci->constr_y = 0;
                mult_ci->constr_z = BelZ::MULT9X9_0_0_Z - BelZ::PADD18_0_0_Z + i;
            }
            // DSP head?
            if (gwu.dsp_bus_src(ci, "SI", 18) == nullptr && gwu.dsp_bus_dst(ci, "SBO", 18) == nullptr) {
                for (int i = 0; i < 18; ++i) {
                    ci->disconnectPort(ctx->idf("SI%d", i));
                    ci->disconnectPort(ctx->idf("SBO%d", i));
                }
                dsp_heads.push_back(ci);
                if (ctx->verbose) {
                    log_info(" found a DSP head: %s\n", ctx->nameOf(ci));
                }
            }
        } break;
        case ID_MULT9X9: {
            pass_net_type(ci, id_ASEL);
            pass_net_type(ci, id_BSEL);
            gwu.remove_brackets(ci);

            // add padd9 as a child
            ci->cluster = ci->name;
            ci->constr_abs_z = false;
            ci->constr_x = 0;
            ci->constr_y = 0;
            ci->constr_z = 0;
            ci->constr_children.clear();

            IdString padd_name = gwu.create_aux_name(ci->name);
            std::unique_ptr<CellInfo> padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL);
            new_cells.push_back(std::move(padd_cell));
            CellInfo *padd_ci = new_cells.back().get();

            padd_ci->cluster = ci->name;
            padd_ci->constr_abs_z = false;
            padd_ci->constr_x = 0;
            padd_ci->constr_y = 0;
            padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::MULT9X9_0_0_Z;

            // DSP head?
            if (gwu.dsp_bus_src(ci, "SIA", 9) == nullptr && gwu.dsp_bus_src(ci, "SIB", 9) == nullptr) {
                for (int i = 0; i < 9; ++i) {
                    ci->disconnectPort(ctx->idf("SIA%d", i));
                    ci->disconnectPort(ctx->idf("SIB%d", i));
                }
                dsp_heads.push_back(ci);
                if (ctx->verbose) {
                    log_info(" found a DSP head: %s\n", ctx->nameOf(ci));
                }
            }
        } break;
        case ID_MULT12X12: {
            gwu.remove_brackets(ci);
        } break;
        case ID_MULT18X18: {
            pass_net_type(ci, id_ASEL);
            pass_net_type(ci, id_BSEL);
            gwu.remove_brackets(ci);

            // add padd9s and mult9s as a children
            ci->cluster = ci->name;
            ci->constr_abs_z = false;
            ci->constr_x = 0;
            ci->constr_y = 0;
            ci->constr_z = 0;
            ci->constr_children.clear();

            for (int i = 0; i < 2; ++i) {
                IdString padd_name = gwu.create_aux_name(ci->name, i * 2);
                std::unique_ptr<CellInfo> padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(padd_cell));
                CellInfo *padd_ci = new_cells.back().get();

                padd_ci->cluster = ci->name;
                padd_ci->constr_abs_z = false;
                padd_ci->constr_x = 0;
                padd_ci->constr_y = 0;
                padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::MULT18X18_0_0_Z + i;

                IdString mult_name = gwu.create_aux_name(ci->name, i * 2 + 1);
                std::unique_ptr<CellInfo> mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(mult_cell));
                CellInfo *mult_ci = new_cells.back().get();

                mult_ci->cluster = ci->name;
                mult_ci->constr_abs_z = false;
                mult_ci->constr_x = 0;
                mult_ci->constr_y = 0;
                mult_ci->constr_z = BelZ::MULT9X9_0_0_Z - BelZ::MULT18X18_0_0_Z + i;
            }

            // DSP head?
            if (gwu.dsp_bus_src(ci, "SIA", 18) == nullptr && gwu.dsp_bus_src(ci, "SIB", 18) == nullptr) {
                for (int i = 0; i < 18; ++i) {
                    ci->disconnectPort(ctx->idf("SIA%d", i));
                    ci->disconnectPort(ctx->idf("SIB%d", i));
                }
                dsp_heads.push_back(ci);
                if (ctx->verbose) {
                    log_info(" found a DSP head: %s\n", ctx->nameOf(ci));
                }
            }
        } break;
        case ID_ALU54D: {
            pass_net_type(ci, id_ACCLOAD);
            gwu.remove_brackets(ci);

            // ACCLOAD - It looks like these wires are always connected to each other.
            ci->cell_bel_pins.at(id_ACCLOAD).clear();
            ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ACCLOAD0);
            ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ACCLOAD1);

            // add padd9s and mult9s as a children
            ci->cluster = ci->name;
            ci->constr_abs_z = false;
            ci->constr_x = 0;
            ci->constr_y = 0;
            ci->constr_z = 0;
            ci->constr_children.clear();

            for (int i = 0; i < 4; ++i) {
                IdString padd_name = gwu.create_aux_name(ci->name, i * 2);
                std::unique_ptr<CellInfo> padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(padd_cell));
                CellInfo *padd_ci = new_cells.back().get();

                padd_ci->cluster = ci->name;
                padd_ci->constr_abs_z = false;
                padd_ci->constr_x = 0;
                padd_ci->constr_y = 0;
                padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::ALU54D_0_Z + 4 * (i / 2) + (i % 2);

                IdString mult_name = gwu.create_aux_name(ci->name, i * 2 + 1);
                std::unique_ptr<CellInfo> mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(mult_cell));
                CellInfo *mult_ci = new_cells.back().get();

                mult_ci->cluster = ci->name;
                mult_ci->constr_abs_z = false;
                mult_ci->constr_x = 0;
                mult_ci->constr_y = 0;
                mult_ci->constr_z = BelZ::MULT9X9_0_0_Z - BelZ::ALU54D_0_Z + 4 * (i / 2) + (i % 2);
            }

            // DSP head?
            if (gwu.dsp_bus_src(ci, "CASI", 55) == nullptr) {
                for (int i = 0; i < 55; ++i) {
                    ci->disconnectPort(ctx->idf("CASI%d", i));
                }
                dsp_heads.push_back(ci);
                if (ctx->verbose) {
                    log_info(" found a DSP head: %s\n", ctx->nameOf(ci));
                }
            }
        } break;
        case ID_MULTALU18X18: {
            // Ports C and D conflict so we need to know the operating mode here.
            if (ci->params.count(id_MULTALU18X18_MODE) == 0) {
                ci->setParam(id_MULTALU18X18_MODE, 0);
            }
            int multalu18x18_mode = ci->params.at(id_MULTALU18X18_MODE).as_int64();
            if (multalu18x18_mode < 0 || multalu18x18_mode > 2) {
                log_error("%s MULTALU18X18_MODE is not in {0, 1, 2}.\n", ctx->nameOf(ci));
            }
            NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();

            for (int i = 0; i < 54; ++i) {
                if (i < 18) {
                    if (multalu18x18_mode != 2) {
                        ci->renamePort(ctx->idf("A[%d]", i), ctx->idf("A%d1", i));
                        ci->renamePort(ctx->idf("B[%d]", i), ctx->idf("B%d1", i));
                    } else {
                        ci->renamePort(ctx->idf("A[%d]", i), ctx->idf("A%d0", i));
                        ci->renamePort(ctx->idf("B[%d]", i), ctx->idf("B%d0", i));
                    }
                }
            }

            gwu.remove_brackets(ci);
            for (int i = 0; i < 54; ++i) {
                switch (multalu18x18_mode) {
                case 0:
                    ci->disconnectPort(ctx->idf("D%d", i));
                    break;
                case 1:
                    ci->disconnectPort(ctx->idf("C%d", i));
                    ci->disconnectPort(ctx->idf("D%d", i));
                    break;
                case 2:
                    ci->disconnectPort(ctx->idf("C%d", i));
                    break;
                default:
                    break;
                }
            }
            if (multalu18x18_mode != 2) {
                ci->renamePort(id_ASIGN, id_ASIGN1);
                ci->renamePort(id_BSIGN, id_BSIGN1);
                ci->addInput(id_ASIGN0);
                ci->addInput(id_BSIGN0);
                ci->connectPort(id_ASIGN0, vss_net);
                ci->connectPort(id_BSIGN0, vss_net);
                ci->disconnectPort(id_DSIGN);
            } else { // BSIGN0 and DSIGN are the same wire
                ci->renamePort(id_ASIGN, id_ASIGN0);
                ci->addInput(id_ASIGN1);
                ci->connectPort(id_ASIGN1, vss_net);
                ci->renamePort(id_BSIGN, id_BSIGN0);
            }

            // ACCLOAD - It looks like these wires are always connected to each other.
            pass_net_type(ci, id_ACCLOAD);
            ci->cell_bel_pins.at(id_ACCLOAD).clear();
            ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ACCLOAD0);
            ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ACCLOAD1);

            // add padd9s and mult9s as a children
            ci->cluster = ci->name;
            ci->constr_abs_z = false;
            ci->constr_x = 0;
            ci->constr_y = 0;
            ci->constr_z = 0;
            ci->constr_children.clear();

            for (int i = 0; i < 2; ++i) {
                IdString padd_name = gwu.create_aux_name(ci->name, i * 2);
                std::unique_ptr<CellInfo> padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(padd_cell));
                CellInfo *padd_ci = new_cells.back().get();

                padd_ci->cluster = ci->name;
                padd_ci->constr_abs_z = false;
                padd_ci->constr_x = 0;
                padd_ci->constr_y = 0;
                padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::MULTALU18X18_0_Z + i;

                IdString mult_name = gwu.create_aux_name(ci->name, i * 2 + 1);
                std::unique_ptr<CellInfo> mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(mult_cell));
                CellInfo *mult_ci = new_cells.back().get();

                mult_ci->cluster = ci->name;
                mult_ci->constr_abs_z = false;
                mult_ci->constr_x = 0;
                mult_ci->constr_y = 0;
                mult_ci->constr_z = BelZ::MULT9X9_0_0_Z - BelZ::MULTALU18X18_0_Z + i;
            }

            // DSP head?
            if (gwu.dsp_bus_src(ci, "CASI", 55) == nullptr) {
                for (int i = 0; i < 55; ++i) {
                    ci->disconnectPort(ctx->idf("CASI%d", i));
                }
                dsp_heads.push_back(ci);
                if (ctx->verbose) {
                    log_info(" found a DSP head: %s\n", ctx->nameOf(ci));
                }
            }
        } break;
        case ID_MULT27X36: {
            // We assemble that primitive from two MultAlu27x18 connected via CASO->CASI.
            IdString l_mult_name = gwu.create_aux_name(ci->name, 1);
            std::unique_ptr<CellInfo> l_mult_cell = gwu.create_cell(l_mult_name, id_MULTALU27X18);
            new_dsps.push_back(std::move(l_mult_cell));
            CellInfo *l_mult = new_dsps.back().get();

            IdString h_mult_name = gwu.create_aux_name(ci->name, 2);
            std::unique_ptr<CellInfo> h_mult_cell = gwu.create_cell(h_mult_name, id_MULTALU27X18);
            new_dsps.push_back(std::move(h_mult_cell));
            CellInfo *h_mult = new_dsps.back().get();

            // SIAs are disconnected
            for (int i = 0; i < 27; ++i) {
                l_mult->addInput(ctx->idf("SIA[%d]", i));
                h_mult->addInput(ctx->idf("SIA[%d]", i));
                l_mult->addOutput(ctx->idf("SOA[%d]", i));
                h_mult->addOutput(ctx->idf("SOA[%d]", i));
            }

            // unused
            l_mult->addInput(id_CSEL);
            h_mult->addInput(id_CSEL);
            l_mult->addInput(id_ASEL);
            h_mult->addInput(id_ASEL);
            l_mult->addInput(id_ACCSEL);
            h_mult->addInput(id_ACCSEL);

            for (int i = 0; i < 48; ++i) {
                // C is disconnected
                l_mult->addInput(ctx->idf("C[%d]", i));
                h_mult->addInput(ctx->idf("C[%d]", i));
                // low mult CASI is disconnected
                l_mult->addInput(ctx->idf("CASI[%d]", i));
                // high mult CASO is disconnected
                h_mult->addOutput(ctx->idf("CASO[%d]", i));
                // low CASO -> high CASI
                l_mult->addOutput(ctx->idf("CASO[%d]", i));
                h_mult->addInput(ctx->idf("CASI[%d]", i));
                l_mult->connectPorts(ctx->idf("CASO[%d]", i), h_mult, ctx->idf("CASI[%d]", i));
            }

            // input A is shared
            ci->movePortBusTo(id_A, 0, 1, l_mult, id_A, 0, 1, 27);
            l_mult->copyPortBusTo(id_A, 0, 1, h_mult, id_A, 0, 1, 27);

            // input D is shared
            ci->movePortBusTo(id_D, 0, 1, l_mult, id_D, 0, 1, 26);
            l_mult->copyPortBusTo(id_D, 0, 1, h_mult, id_D, 0, 1, 26);

            // input PSEL is shared
            ci->movePortTo(id_PSEL, l_mult, id_PSEL);
            l_mult->copyPortBusTo(id_PSEL, 0, 1, h_mult, id_PSEL, 0, 1, 26);

            // input PADDSUB is shared
            ci->movePortTo(id_PADDSUB, l_mult, id_PADDSUB);
            l_mult->copyPortTo(id_PADDSUB, h_mult, id_PADDSUB);

            // input B is divided
            ci->movePortBusTo(id_B, 0, 1, l_mult, id_B, 0, 1, 18);
            ci->movePortBusTo(id_B, 18, 1, h_mult, id_B, 0, 1, 18);

            // output DOUT is divided
            ci->movePortBusTo(id_DOUT, 0, 1, l_mult, id_DOUT, 0, 1, 18);
            ci->movePortBusTo(id_DOUT, 18, 1, h_mult, id_DOUT, 0, 1, 47);

            // Control inputs are shared
            ci->movePortBusTo(id_CLK, 0, 1, l_mult, id_CLK, 0, 1, 2);
            l_mult->copyPortBusTo(id_CLK, 0, 1, h_mult, id_CLK, 0, 1, 2);
            ci->movePortBusTo(id_CE, 0, 1, l_mult, id_CE, 0, 1, 2);
            l_mult->copyPortBusTo(id_CE, 0, 1, h_mult, id_CE, 0, 1, 2);
            ci->movePortBusTo(id_RESET, 0, 1, l_mult, id_RESET, 0, 1, 2);
            l_mult->copyPortBusTo(id_RESET, 0, 1, h_mult, id_RESET, 0, 1, 2);

            // copy params
            for (auto param : ci->params) {
                l_mult->setParam(param.first, param.second);
                h_mult->setParam(param.first, param.second);
            }
            // mark as MULT27X36
            // we will catch these attributes during packing and add the missing parameters
            l_mult->setAttr(id_MULT27X36_MAIN, 1);
            h_mult->setAttr(id_MULT27X36_AUX, 1);

            // remove former Mult27x36
            cells_to_remove.push_back(ci->name);
        } break;
        case ID_MULTALU27X18: {
            gwu.remove_brackets(ci);
            pass_net_type(ci, id_ACCSEL);
            ci->cell_bel_pins[id_ACCSEL].clear();
            ci->cell_bel_pins.at(id_ACCSEL).push_back(id_ACCSEL0);
            ci->cell_bel_pins.at(id_ACCSEL).push_back(id_ACCSEL1);

            // mark 2 mult12x12 as parts of the cluster to prevent
            // other multipliers from being placed there
            ci->cluster = ci->name;
            ci->constr_abs_z = false;
            ci->constr_x = 0;
            ci->constr_y = 0;
            ci->constr_z = 0;
            ci->constr_children.clear();

            for (int i = 0; i < 2; ++i) {
                IdString mult12x12_name = gwu.create_aux_name(ci->name, i * 2);
                std::unique_ptr<CellInfo> mult12x12_cell = gwu.create_cell(mult12x12_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(mult12x12_cell));
                CellInfo *mult12x12_ci = new_cells.back().get();

                mult12x12_ci->cluster = ci->name;
                mult12x12_ci->constr_abs_z = false;
                mult12x12_ci->constr_x = 0;
                mult12x12_ci->constr_y = 0;
                mult12x12_ci->constr_z = BelZ::MULT12X12_0_Z - BelZ::MULTALU27X18_Z + i;
                if (ctx->verbose) {
                    log_info("  mark %s multiplier as used by %s\n", mult12x12_name.c_str(ctx), ctx->nameOf(ci));
                }
            }

            // DSP head? This primitive has the ability to form chains using both SO[AB] -> SI[AB] and
            // CASO->CASI
            bool cas_head = false;
            if (gwu.dsp_bus_src(ci, "CASI", 48) == nullptr) {
                for (int i = 0; i < 48; ++i) {
                    ci->disconnectPort(ctx->idf("CASI%d", i));
                }
                cas_head = true;
            }
            bool so_head = false;
            if (gwu.dsp_bus_src(ci, "SIA", 27) == nullptr) {
                for (int i = 0; i < 27; ++i) {
                    ci->disconnectPort(ctx->idf("SIA%d", i));
                }
                so_head = true;
            }
            if (cas_head && so_head) {
                dsp_heads.push_back(ci);
                if (ctx->verbose) {
                    log_info(" found a DSP head: %s\n", ctx->nameOf(ci));
                }
            }
        } break;
        case ID_MULTALU36X18: {
            if (ci->params.count(id_MULTALU18X18_MODE) == 0) {
                ci->setParam(id_MULTALU18X18_MODE, 0);
            }
            int multalu36x18_mode = ci->params.at(id_MULTALU36X18_MODE).as_int64();
            if (multalu36x18_mode < 0 || multalu36x18_mode > 2) {
                log_error("%s MULTALU36X18_MODE is not in {0, 1, 2}.\n", ctx->nameOf(ci));
            }
            NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();

            gwu.remove_brackets(ci);
            for (int i = 0; i < 36; ++i) {
                if (i < 18) {
                    ci->cell_bel_pins.at(ctx->idf("A[%d]", i)).clear();
                    ci->cell_bel_pins[ctx->idf("A%d", i)].push_back(ctx->idf("A%d0", i));
                    ci->cell_bel_pins.at(ctx->idf("A%d", i)).push_back(ctx->idf("A%d1", i));
                }
            }
            for (int i = 0; i < 54; ++i) {
                switch (multalu36x18_mode) {
                case 1: /* fallthrough */
                case 2:
                    ci->disconnectPort(ctx->idf("C%d", i));
                    break;
                default:
                    break;
                }
            }

            // both A have sign bit
            // only MSB part of B has sign bit
            ci->cell_bel_pins.at(id_ASIGN).clear();
            ci->cell_bel_pins.at(id_ASIGN).push_back(id_ASIGN0);
            ci->cell_bel_pins.at(id_ASIGN).push_back(id_ASIGN1);
            ci->renamePort(id_BSIGN, id_BSIGN1);
            ci->addInput(id_BSIGN0);
            ci->connectPort(id_BSIGN0, vss_net);

            pass_net_type(ci, id_ACCLOAD);
            if (multalu36x18_mode == 1) {
                if (ci->attrs.at(id_NET_ACCLOAD).as_string() == "GND" ||
                    ci->attrs.at(id_NET_ACCLOAD).as_string() == "VCC") {
                    ci->disconnectPort(id_ACCLOAD);
                } else {
                    ci->addInput(id_ALUSEL4);
                    ci->addInput(id_ALUSEL6);
                    ci->cell_bel_pins.at(id_ACCLOAD).clear();
                    ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ALUSEL4);
                    ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ALUSEL6);
                }
            } else {
                ci->disconnectPort(id_ACCLOAD);
            }

            // add padd9s and mult9s as a children
            ci->cluster = ci->name;
            ci->constr_abs_z = false;
            ci->constr_x = 0;
            ci->constr_y = 0;
            ci->constr_z = 0;
            ci->constr_children.clear();

            for (int i = 0; i < 2; ++i) {
                IdString padd_name = gwu.create_aux_name(ci->name, i * 2);
                std::unique_ptr<CellInfo> padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(padd_cell));
                CellInfo *padd_ci = new_cells.back().get();

                padd_ci->cluster = ci->name;
                padd_ci->constr_abs_z = false;
                padd_ci->constr_x = 0;
                padd_ci->constr_y = 0;
                padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::MULTALU36X18_0_Z + i;

                IdString mult_name = gwu.create_aux_name(ci->name, i * 2 + 1);
                std::unique_ptr<CellInfo> mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(mult_cell));
                CellInfo *mult_ci = new_cells.back().get();

                mult_ci->cluster = ci->name;
                mult_ci->constr_abs_z = false;
                mult_ci->constr_x = 0;
                mult_ci->constr_y = 0;
                mult_ci->constr_z = BelZ::MULT9X9_0_0_Z - BelZ::MULTALU36X18_0_Z + i;
            }

            // DSP head?
            if (gwu.dsp_bus_src(ci, "CASI", 55) == nullptr) {
                for (int i = 0; i < 55; ++i) {
                    ci->disconnectPort(ctx->idf("CASI%d", i));
                }
                dsp_heads.push_back(ci);
                if (ctx->verbose) {
                    log_info(" found a DSP head: %s\n", ctx->nameOf(ci));
                }
            }
        } break;
        case ID_MULTADDALU12X12: {
            gwu.remove_brackets(ci);
            pass_net_type(ci, id_ACCSEL);

            ci->cell_bel_pins.at(id_ACCSEL).clear();
            ci->cell_bel_pins.at(id_ACCSEL).push_back(id_ACCSEL0);
            ci->cell_bel_pins.at(id_ACCSEL).push_back(id_ACCSEL1);

            // mark 2 mult12x12 as parts of the cluster to prevent
            // other multipliers from being placed there
            ci->cluster = ci->name;
            ci->constr_abs_z = false;
            ci->constr_x = 0;
            ci->constr_y = 0;
            ci->constr_z = 0;
            ci->constr_children.clear();

            for (int i = 0; i < 2; ++i) {
                IdString mult12x12_name = gwu.create_aux_name(ci->name, i * 2);
                std::unique_ptr<CellInfo> mult12x12_cell = gwu.create_cell(mult12x12_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(mult12x12_cell));
                CellInfo *mult12x12_ci = new_cells.back().get();

                mult12x12_ci->cluster = ci->name;
                mult12x12_ci->constr_abs_z = false;
                mult12x12_ci->constr_x = 0;
                mult12x12_ci->constr_y = 0;
                mult12x12_ci->constr_z = BelZ::MULT12X12_0_Z - BelZ::MULTADDALU12X12_Z + i;
            }

            // DSP head?
            if (gwu.dsp_bus_src(ci, "CASI", 48) == nullptr) {
                for (int i = 0; i < 48; ++i) {
                    ci->disconnectPort(ctx->idf("CASI%d", i));
                }
                dsp_heads.push_back(ci);
                if (ctx->verbose) {
                    log_info(" found a DSP head: %s\n", ctx->nameOf(ci));
                }
            }

        } break;
        case ID_MULTADDALU18X18: {
            if (ci->params.count(id_MULTADDALU18X18_MODE) == 0) {
                ci->setParam(id_MULTADDALU18X18_MODE, 0);
            }
            int multaddalu18x18_mode = ci->params.at(id_MULTADDALU18X18_MODE).as_int64();
            if (multaddalu18x18_mode < 0 || multaddalu18x18_mode > 2) {
                log_error("%s MULTADDALU18X18_MODE is not in {0, 1, 2}.\n", ctx->nameOf(ci));
            }
            for (int i = 0; i < 54; ++i) {
                if (i < 18) {
                    ci->renamePort(ctx->idf("A0[%d]", i), ctx->idf("A%d0", i));
                    ci->renamePort(ctx->idf("B0[%d]", i), ctx->idf("B%d0", i));
                    ci->renamePort(ctx->idf("A1[%d]", i), ctx->idf("A%d1", i));
                    ci->renamePort(ctx->idf("B1[%d]", i), ctx->idf("B%d1", i));
                }
                if (multaddalu18x18_mode == 0) {
                    ci->renamePort(ctx->idf("C[%d]", i), ctx->idf("C%d", i));
                } else {
                    ci->disconnectPort(ctx->idf("C[%d]", i));
                }
            }
            gwu.remove_brackets(ci);

            pass_net_type(ci, id_ASEL0);
            pass_net_type(ci, id_ASEL1);
            pass_net_type(ci, id_BSEL0);
            pass_net_type(ci, id_BSEL1);
            pass_net_type(ci, id_ACCLOAD);
            if (multaddalu18x18_mode == 1) {
                if (ci->attrs.at(id_NET_ACCLOAD).as_string() == "GND" ||
                    ci->attrs.at(id_NET_ACCLOAD).as_string() == "VCC") {
                    ci->disconnectPort(id_ACCLOAD);
                } else {
                    ci->addInput(id_ALUSEL4);
                    ci->addInput(id_ALUSEL6);
                    ci->cell_bel_pins.at(id_ACCLOAD).clear();
                    ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ALUSEL4);
                    ci->cell_bel_pins.at(id_ACCLOAD).push_back(id_ALUSEL6);
                }
            } else {
                ci->disconnectPort(id_ACCLOAD);
            }

            // add padd9s and mult9s as a children
            ci->cluster = ci->name;
            ci->constr_abs_z = false;
            ci->constr_x = 0;
            ci->constr_y = 0;
            ci->constr_z = 0;
            ci->constr_children.clear();

            for (int i = 0; i < 2; ++i) {
                IdString padd_name = gwu.create_aux_name(ci->name, i * 2);
                std::unique_ptr<CellInfo> padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(padd_cell));
                CellInfo *padd_ci = new_cells.back().get();

                padd_ci->cluster = ci->name;
                padd_ci->constr_abs_z = false;
                padd_ci->constr_x = 0;
                padd_ci->constr_y = 0;
                padd_ci->constr_z = BelZ::PADD9_0_0_Z - BelZ::MULTADDALU18X18_0_Z + i;

                IdString mult_name = gwu.create_aux_name(ci->name, i * 2 + 1);
                std::unique_ptr<CellInfo> mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(mult_cell));
                CellInfo *mult_ci = new_cells.back().get();

                mult_ci->cluster = ci->name;
                mult_ci->constr_abs_z = false;
                mult_ci->constr_x = 0;
                mult_ci->constr_y = 0;
                mult_ci->constr_z = BelZ::MULT9X9_0_0_Z - BelZ::MULTADDALU18X18_0_Z + i;
            }

            // DSP head? This primitive has the ability to form chains using both SO[AB] -> SI[AB] and
            // CASO->CASI
            bool cas_head = false;
            if (gwu.dsp_bus_src(ci, "CASI", 55) == nullptr) {
                for (int i = 0; i < 55; ++i) {
                    ci->disconnectPort(ctx->idf("CASI%d", i));
                }
                cas_head = true;
            }
            bool so_head = false;
            if (gwu.dsp_bus_src(ci, "SIA", 18) == nullptr && gwu.dsp_bus_src(ci, "SIB", 18) == nullptr) {
                for (int i = 0; i < 18; ++i) {
                    ci->disconnectPort(ctx->idf("SIA%d", i));
                    ci->disconnectPort(ctx->idf("SIB%d", i));
                }
                so_head = true;
            }
            if (cas_head && so_head) {
                dsp_heads.push_back(ci);
                if (ctx->verbose) {
                    log_info(" found a DSP head: %s\n", ctx->nameOf(ci));
                }
            }
        } break;
        case ID_MULT36X36: {
            for (int i = 0; i < 36; ++i) {
                ci->cell_bel_pins.at(ctx->idf("A[%d]", i)).clear();
                ci->cell_bel_pins.at(ctx->idf("A[%d]", i)).push_back(ctx->idf("A%d0", i));
                ci->cell_bel_pins.at(ctx->idf("A[%d]", i)).push_back(ctx->idf("A%d1", i));
                ci->cell_bel_pins.at(ctx->idf("B[%d]", i)).clear();
                ci->cell_bel_pins.at(ctx->idf("B[%d]", i)).push_back(ctx->idf("B%d0", i));
                ci->cell_bel_pins.at(ctx->idf("B[%d]", i)).push_back(ctx->idf("B%d1", i));
            }
            // only MSB sign bits
            ci->cell_bel_pins.at(id_ASIGN).clear();
            ci->cell_bel_pins.at(id_ASIGN).push_back(id_ASIGN0);
            ci->cell_bel_pins.at(id_ASIGN).push_back(id_ASIGN1);
            ci->cell_bel_pins.at(id_BSIGN).clear();
            ci->cell_bel_pins.at(id_BSIGN).push_back(id_BSIGN0);
            ci->cell_bel_pins.at(id_BSIGN).push_back(id_BSIGN1);

            // LSB sign bits = 0
            NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();
            ci->addInput(id_ZERO_SIGN);
            ci->cell_bel_pins[id_ZERO_SIGN].push_back(id_ZERO_ASIGN0);
            ci->cell_bel_pins.at(id_ZERO_SIGN).push_back(id_ZERO_BSIGN0);
            ci->cell_bel_pins.at(id_ZERO_SIGN).push_back(id_ZERO_BSIGN1);
            ci->cell_bel_pins.at(id_ZERO_SIGN).push_back(id_ZERO_ASIGN1);
            ci->connectPort(id_ZERO_SIGN, vss_net);

            for (int i = 0; i < 72; ++i) {
                ci->renamePort(ctx->idf("DOUT[%d]", i), ctx->idf("DOUT%d", i));
            }

            // add padd9s and mult9s as a children
            ci->cluster = ci->name;
            ci->constr_abs_z = false;
            ci->constr_x = 0;
            ci->constr_y = 0;
            ci->constr_z = 0;
            ci->constr_children.clear();

            for (int i = 0; i < 8; ++i) {
                IdString padd_name = gwu.create_aux_name(ci->name, i * 2);
                std::unique_ptr<CellInfo> padd_cell = gwu.create_cell(padd_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(padd_cell));
                CellInfo *padd_ci = new_cells.back().get();

                static int padd_z[] = {BelZ::PADD9_0_0_Z, BelZ::PADD9_0_2_Z, BelZ::PADD9_1_0_Z, BelZ::PADD9_1_2_Z};
                padd_ci->cluster = ci->name;
                padd_ci->constr_abs_z = false;
                padd_ci->constr_x = 0;
                padd_ci->constr_y = 0;
                padd_ci->constr_z = padd_z[i / 2] - BelZ::MULT36X36_Z + i % 2;

                IdString mult_name = gwu.create_aux_name(ci->name, i * 2 + 1);
                std::unique_ptr<CellInfo> mult_cell = gwu.create_cell(mult_name, id_DUMMY_CELL);
                new_cells.push_back(std::move(mult_cell));
                CellInfo *mult_ci = new_cells.back().get();

                static int mult_z[] = {BelZ::MULT9X9_0_0_Z, BelZ::MULT9X9_0_2_Z, BelZ::MULT9X9_1_0_Z,
                                       BelZ::MULT9X9_1_2_Z};
                mult_ci->cluster = ci->name;
                mult_ci->constr_abs_z = false;
                mult_ci->constr_x = 0;
                mult_ci->constr_y = 0;
                mult_ci->constr_z = mult_z[i / 2] - BelZ::MULT36X36_Z + i % 2;
            }
        } break;
        default:
            log_error("Unsupported DSP type '%s'\n", ci->type.c_str(ctx));
        }
    };

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

    // Process new DSPs if any are generated
    for (auto &cell : new_dsps) {
        IdString name = cell->name;
        ctx->cells[name] = std::move(cell);
        auto ci = ctx->cells.at(name).get();
        process_dsp_cell(ci);
    }

    for (auto cell : cells_to_remove) {
        ctx->cells.erase(cell);
    }

    // add new cells
    for (auto &cell : new_cells) {
        if (cell->cluster != ClusterId()) {
            IdString cluster_root = cell->cluster;
            IdString cell_name = cell->name;
            ctx->cells[cell_name] = std::move(cell);
            ctx->cells.at(cluster_root).get()->constr_children.push_back(ctx->cells.at(cell_name).get());
        } else {
            ctx->cells[cell->name] = std::move(cell);
        }
    }

    // make CASO->CASI only chain from DSPs
    auto make_CAS_chain = [&](CellInfo *head, int wire_num) {
        CellInfo *cur_dsp = head;
        while (1) {
            CellInfo *next_dsp = gwu.dsp_bus_dst(cur_dsp, "CASO", wire_num);
            if (next_dsp == nullptr) {
                // End of chain
                for (int i = 0; i < wire_num; ++i) {
                    cur_dsp->disconnectPort(ctx->idf("CASO%d", i));
                }
                break;
            }
            for (int i = 0; i < wire_num; ++i) {
                cur_dsp->disconnectPort(ctx->idf("CASO%d", i));
                next_dsp->disconnectPort(ctx->idf("CASI%d", i));
            }
            cur_dsp->setAttr(id_USE_CASCADE_OUT, 1);
            cur_dsp = next_dsp;
            cur_dsp->setAttr(id_USE_CASCADE_IN, 1);
            if (ctx->verbose) {
                log_info("  add %s to the chain.\n", ctx->nameOf(cur_dsp));
            }
            if (head->cluster == ClusterId()) {
                head->cluster = head->name;
            }
            cur_dsp->cluster = head->name;
            head->constr_children.push_back(cur_dsp);
            for (auto child : cur_dsp->constr_children) {
                child->cluster = head->name;
                head->constr_children.push_back(child);
            }
            cur_dsp->constr_children.clear();
        }
    };

    // make combined CASO->CASI and SOA->SIA chain
    auto make_SIA_CAS_chain = [&](CellInfo *head, int cas_wire_num, int sia_wire_num, bool use_sib = true) {
        CellInfo *cur_dsp = head;
        while (1) {
            bool end_of_cas_chain = false;
            int wire_num = cas_wire_num;
            CellInfo *next_dsp_a = gwu.dsp_bus_dst(cur_dsp, "CASO", wire_num);
            if (next_dsp_a == nullptr) {
                // End of CASO chain
                for (int i = 0; i < wire_num; ++i) {
                    cur_dsp->disconnectPort(ctx->idf("CASO%d", i));
                }
                end_of_cas_chain = true;
            } else {
                for (int i = 0; i < wire_num; ++i) {
                    cur_dsp->disconnectPort(ctx->idf("CASO%d", i));
                    next_dsp_a->disconnectPort(ctx->idf("CASI%d", i));
                }
            }

            bool end_of_so_chain = false;
            wire_num = sia_wire_num;
            CellInfo *next_so_dsp_a = gwu.dsp_bus_dst(cur_dsp, "SOA", wire_num);
            CellInfo *next_so_dsp_b = use_sib ? gwu.dsp_bus_dst(cur_dsp, "SOB", wire_num) : nullptr;
            if (next_so_dsp_a != nullptr && next_so_dsp_b != nullptr && next_so_dsp_a != next_so_dsp_b) {
                log_error("%s is the source for two different DSPs (%s and %s) in the chain.", ctx->nameOf(cur_dsp),
                          ctx->nameOf(next_so_dsp_a), ctx->nameOf(next_so_dsp_b));
            }
            if (next_so_dsp_a == nullptr && (!use_sib || (use_sib && next_so_dsp_b == nullptr))) {
                // End of SO chain
                for (int i = 0; i < wire_num; ++i) {
                    cur_dsp->disconnectPort(ctx->idf("SOA%d", i));
                    if (use_sib) {
                        cur_dsp->disconnectPort(ctx->idf("SOB%d", i));
                    }
                }
                end_of_so_chain = true;
            } else {
                next_so_dsp_a = next_so_dsp_a != nullptr ? next_so_dsp_a : next_so_dsp_b;
                for (int i = 0; i < wire_num; ++i) {
                    cur_dsp->disconnectPort(ctx->idf("SOA%d", i));
                    next_so_dsp_a->disconnectPort(ctx->idf("SIA%d", i));
                    if (use_sib) {
                        cur_dsp->disconnectPort(ctx->idf("SOB%d", i));
                        next_so_dsp_a->disconnectPort(ctx->idf("SIB%d", i));
                    }
                }
            }
            if (end_of_cas_chain && end_of_so_chain) {
                break;
            }

            // to the next
            if (!end_of_cas_chain) {
                cur_dsp->setAttr(id_USE_CASCADE_OUT, 1);
            }
            cur_dsp = next_dsp_a != nullptr ? next_dsp_a : next_so_dsp_a;
            if (!end_of_cas_chain) {
                cur_dsp->setAttr(id_USE_CASCADE_IN, 1);
            }
            if (ctx->verbose) {
                log_info("  add %s to the chain. End of the SO chain:%d, end of the CAS chain:%d\n",
                         ctx->nameOf(cur_dsp), end_of_so_chain, end_of_cas_chain);
            }
            if (head->cluster == ClusterId()) {
                head->cluster = head->name;
            }
            cur_dsp->cluster = head->name;
            head->constr_children.push_back(cur_dsp);
            for (auto child : cur_dsp->constr_children) {
                child->cluster = head->name;
                head->constr_children.push_back(child);
            }
            cur_dsp->constr_children.clear();
        }
    };

    // DSP chains
    for (CellInfo *head : dsp_heads) {
        if (ctx->verbose) {
            log_info("Process a DSP head: %s\n", ctx->nameOf(head));
        }
        switch (head->type.hash()) {
        case ID_PADD9: /* fallthrough */
        case ID_PADD18: {
            int wire_num = 9;
            if (head->type == id_PADD18) {
                wire_num = 18;
            }

            CellInfo *cur_dsp = head;
            while (1) {
                CellInfo *next_dsp_a = gwu.dsp_bus_dst(cur_dsp, "SO", wire_num);
                CellInfo *next_dsp_b = gwu.dsp_bus_src(cur_dsp, "SBI", wire_num);
                if (next_dsp_a != nullptr && next_dsp_b != nullptr && next_dsp_a != next_dsp_b) {
                    log_error("%s is the next for two different DSPs (%s and %s) in the chain.", ctx->nameOf(cur_dsp),
                              ctx->nameOf(next_dsp_a), ctx->nameOf(next_dsp_b));
                }
                if (next_dsp_a == nullptr && next_dsp_b == nullptr) {
                    // End of chain
                    cur_dsp->setAttr(id_LAST_IN_CHAIN, 1);
                    for (int i = 0; i < wire_num; ++i) {
                        cur_dsp->disconnectPort(ctx->idf("SO%d", i));
                        cur_dsp->disconnectPort(ctx->idf("SBI%d", i));
                    }
                    break;
                }

                next_dsp_a = next_dsp_a != nullptr ? next_dsp_a : next_dsp_b;
                for (int i = 0; i < wire_num; ++i) {
                    cur_dsp->disconnectPort(ctx->idf("SO%d", i));
                    cur_dsp->disconnectPort(ctx->idf("SBI%d", i));
                    next_dsp_a->disconnectPort(ctx->idf("SI%d", i));
                    next_dsp_a->disconnectPort(ctx->idf("SBO%d", i));
                }
                cur_dsp = next_dsp_a;
                if (ctx->verbose) {
                    log_info("  add %s to the chain.\n", ctx->nameOf(cur_dsp));
                }
                if (head->cluster == ClusterId()) {
                    head->cluster = head->name;
                }
                cur_dsp->cluster = head->name;
                head->constr_children.push_back(cur_dsp);
                for (auto child : cur_dsp->constr_children) {
                    child->cluster = head->name;
                    head->constr_children.push_back(child);
                }
                cur_dsp->constr_children.clear();
            }
        } break;
        case ID_MULT9X9: /* fallthrough */
        case ID_MULT18X18: {
            int wire_num = 9;
            if (head->type == id_MULT18X18) {
                wire_num = 18;
            }

            CellInfo *cur_dsp = head;
            while (1) {
                CellInfo *next_dsp_a = gwu.dsp_bus_dst(cur_dsp, "SOA", wire_num);
                CellInfo *next_dsp_b = gwu.dsp_bus_dst(cur_dsp, "SOB", wire_num);
                if (next_dsp_a != nullptr && next_dsp_b != nullptr && next_dsp_a != next_dsp_b) {
                    log_error("%s is the source for two different DSPs (%s and %s) in the chain.", ctx->nameOf(cur_dsp),
                              ctx->nameOf(next_dsp_a), ctx->nameOf(next_dsp_b));
                }
                if (next_dsp_a == nullptr && next_dsp_b == nullptr) {
                    // End of chain
                    for (int i = 0; i < wire_num; ++i) {
                        cur_dsp->disconnectPort(ctx->idf("SOA%d", i));
                        cur_dsp->disconnectPort(ctx->idf("SOB%d", i));
                    }
                    break;
                }

                next_dsp_a = next_dsp_a != nullptr ? next_dsp_a : next_dsp_b;
                for (int i = 0; i < wire_num; ++i) {
                    cur_dsp->disconnectPort(ctx->idf("SOA%d", i));
                    cur_dsp->disconnectPort(ctx->idf("SOB%d", i));
                    next_dsp_a->disconnectPort(ctx->idf("SIA%d", i));
                    next_dsp_a->disconnectPort(ctx->idf("SIB%d", i));
                }
                cur_dsp = next_dsp_a;
                if (ctx->verbose) {
                    log_info("  add %s to the chain.\n", ctx->nameOf(cur_dsp));
                }
                if (head->cluster == ClusterId()) {
                    head->cluster = head->name;
                }
                cur_dsp->cluster = head->name;
                head->constr_children.push_back(cur_dsp);
                for (auto child : cur_dsp->constr_children) {
                    child->cluster = head->name;
                    head->constr_children.push_back(child);
                }
                cur_dsp->constr_children.clear();
            }
        } break;
        case ID_MULTALU18X18: /* fallthrough */
        case ID_MULTALU36X18: /* fallthrough */
        case ID_ALU54D: {
            make_CAS_chain(head, 55);
        } break;
        case ID_MULTADDALU12X12: {
            make_CAS_chain(head, 48);
        } break;
        case ID_MULTALU27X18: {
            // This primitive has the ability to form chains using both SO[AB] -> SI[AB] and CASO->CASI
            make_SIA_CAS_chain(head, 48, 27, false);
        } break;
        case ID_MULTADDALU18X18: {
            // This primitive has the ability to form chains using both SO[AB] -> SI[AB] and CASO->CASI
            make_SIA_CAS_chain(head, 55, 18);
        } break;
        }
    }
}
NEXTPNR_NAMESPACE_END
