#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 NEXTPNR_NAMESPACE_BEGIN // =================================== // IO logic // =================================== // the functions of these two inputs are yet to be discovered, so we set as observed // in the exemplary images void GowinPacker::set_daaj_nets(CellInfo &ci, BelId bel) { std::vector pins = ctx->getBelPins(bel); if (std::find(pins.begin(), pins.end(), id_DAADJ0) != pins.end()) { ci.addInput(id_DAADJ0); ci.connectPort(id_DAADJ0, ctx->nets.at(ctx->id("$PACKER_GND")).get()); } if (std::find(pins.begin(), pins.end(), id_DAADJ1) != pins.end()) { ci.addInput(id_DAADJ1); ci.connectPort(id_DAADJ1, ctx->nets.at(ctx->id("$PACKER_VCC")).get()); } } BelId GowinPacker::get_iologico_bel(CellInfo *iob) { NPNR_ASSERT(iob->bel != BelId()); Loc loc = ctx->getBelLocation(iob->bel); loc.z = loc.z - BelZ::IOBA_Z + BelZ::IOLOGICA_Z; BelId bel = ctx->getBelByLocation(loc); if (bel != BelId()) { if (ctx->getBelType(bel) == id_IOLOGICO) { return bel; } } return BelId(); } BelId GowinPacker::get_iologici_bel(CellInfo *iob) { NPNR_ASSERT(iob->bel != BelId()); Loc loc = ctx->getBelLocation(iob->bel); loc.z = loc.z - BelZ::IOBA_Z + BelZ::IOLOGICA_Z + 2; BelId bel = ctx->getBelByLocation(loc); if (bel != BelId()) { if (ctx->getBelType(bel) == id_IOLOGICI) { return bel; } } return BelId(); } void GowinPacker::check_iologic_placement(CellInfo &ci, Loc iob_loc, int diff /* 1 - diff */) { if (ci.type.in(id_ODDR, id_ODDRC, id_IDDR, id_IDDRC, id_OSER4, id_IOLOGICI_EMPTY, id_IOLOGICO_EMPTY) || diff) { return; } BelId l_bel = ctx->getBelByLocation(Loc(iob_loc.x, iob_loc.y, BelZ::IOBA_Z + 1 - (iob_loc.z - BelZ::IOBA_Z))); if (!ctx->checkBelAvail(l_bel)) { log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), ctx->nameOfBel(l_bel), ctx->nameOf(ctx->getBoundBelCell(l_bel))); } } // While we require an exact match of the type, in the future the criteria // may be relaxed and there will be a comparison of the control networks // used. bool are_iologic_compatible(CellInfo *ci_0, CellInfo *ci_1) { switch (ci_0->type.hash()) { case ID_ODDR: return ci_1->type == id_IDDR; case ID_ODDRC: return ci_1->type == id_IDDRC; case ID_IDDR: return ci_1->type == id_ODDR; case ID_IDDRC: return ci_1->type == id_ODDRC; default: return false; } return false; } void GowinPacker::pack_bi_output_iol(CellInfo &ci, std::vector &nets_to_remove) { // These primitives have an additional pin to control the tri-state iob - Q1. IdString out_port = id_Q0; IdString tx_port = id_Q1; CellInfo *out_iob = net_only_drives(ctx, ci.ports.at(out_port).net, is_iob, id_I, true); NPNR_ASSERT(out_iob != nullptr && out_iob->bel != BelId()); BelId iob_bel = out_iob->bel; BelId l_bel = get_iologico_bel(out_iob); // check compatible Input and Output iologic if any BelId in_l_bel = get_iologici_bel(out_iob); if (in_l_bel != BelId() && !ctx->checkBelAvail(in_l_bel)) { CellInfo *in_iologic_ci = ctx->getBoundBelCell(in_l_bel); if (!are_iologic_compatible(&ci, in_iologic_ci)) { log_error("IOLOGIC %s at %s cannot coexist with %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel), ctx->nameOf(in_iologic_ci)); } } if (l_bel == BelId()) { log_error("Can't place IOLOGIC %s at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel)); } // mark IOB as used by IOLOGIC out_iob->setParam(id_IOLOGIC_IOB, 1); check_iologic_placement(ci, ctx->getBelLocation(iob_bel), out_iob->params.count(id_DIFF_TYPE) || out_iob->params.count(id_MIPI_OBUF)); if (!ctx->checkBelAvail(l_bel)) { log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), ctx->nameOfBel(l_bel), ctx->nameOf(ctx->getBoundBelCell(l_bel))); } ctx->bindBel(l_bel, &ci, PlaceStrength::STRENGTH_LOCKED); std::string out_mode; switch (ci.type.hash()) { case ID_ODDR: case ID_ODDRC: out_mode = "ODDRX1"; break; case ID_OSER4: out_mode = "ODDRX2"; break; case ID_OSER8: out_mode = "ODDRX4"; break; } ci.setParam(ctx->id("OUTMODE"), out_mode); // disconnect Q output: it is wired internally nets_to_remove.push_back(ci.getPort(out_port)->name); out_iob->disconnectPort(id_I); ci.disconnectPort(out_port); set_daaj_nets(ci, iob_bel); Loc io_loc = ctx->getBelLocation(iob_bel); if (io_loc.y == ctx->getGridDimY() - 1) { config_bottom_row(*out_iob, io_loc, Bottom_io_POD::DDR); } // if Q1 is connected then disconnect it too if (gwu.port_used(&ci, tx_port)) { NPNR_ASSERT(out_iob == net_only_drives(ctx, ci.ports.at(tx_port).net, is_iob, id_OEN, true)); nets_to_remove.push_back(ci.getPort(tx_port)->name); out_iob->disconnectPort(id_OEN); ci.disconnectPort(tx_port); } else { // disconnect TXx ports, ignore these nets switch (ci.type.hash()) { case ID_OSER8: ci.disconnectPort(id_TX3); ci.disconnectPort(id_TX2); /* fall-through */ case ID_OSER4: ci.disconnectPort(id_TX1); ci.disconnectPort(id_TX0); break; case ID_ODDR: /* fall-through */ case ID_ODDRC: /* fall-through */ ci.disconnectPort(id_TX); break; } } make_iob_nets(*out_iob); } void GowinPacker::pack_single_output_iol(CellInfo &ci, std::vector &nets_to_remove) { IdString out_port = id_Q; CellInfo *out_iob = net_only_drives(ctx, ci.ports.at(out_port).net, is_iob, id_I, true); NPNR_ASSERT(out_iob != nullptr && out_iob->bel != BelId()); BelId iob_bel = out_iob->bel; BelId l_bel = get_iologico_bel(out_iob); if (l_bel == BelId()) { log_error("Can't place IOLOGIC %s at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel)); } // mark IOB as used by IOLOGIC out_iob->setParam(id_IOLOGIC_IOB, 1); check_iologic_placement(ci, ctx->getBelLocation(iob_bel), out_iob->params.count(id_DIFF_TYPE) || out_iob->params.count(id_MIPI_OBUF)); if (!ctx->checkBelAvail(l_bel)) { log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), ctx->nameOfBel(l_bel), ctx->nameOf(ctx->getBoundBelCell(l_bel))); } ctx->bindBel(l_bel, &ci, PlaceStrength::STRENGTH_LOCKED); std::string out_mode; switch (ci.type.hash()) { case ID_IOLOGICO_EMPTY: out_mode = "EMPTY"; break; case ID_OVIDEO: out_mode = "VIDEOTX"; break; case ID_OSER10: out_mode = "ODDRX5"; break; } ci.setParam(ctx->id("OUTMODE"), out_mode); // disconnect Q output: it is wired internally nets_to_remove.push_back(ci.getPort(out_port)->name); out_iob->disconnectPort(id_I); ci.disconnectPort(out_port); if (ci.type == id_IOLOGICO_EMPTY) { if (ci.attrs.count(id_HAS_REG) == 0) { ci.movePortTo(id_D, out_iob, id_I); } return; } set_daaj_nets(ci, iob_bel); Loc io_loc = ctx->getBelLocation(iob_bel); if (io_loc.y == ctx->getGridDimY() - 1) { config_bottom_row(*out_iob, io_loc, Bottom_io_POD::DDR); } make_iob_nets(*out_iob); } BelId GowinPacker::get_aux_iologic_bel(const CellInfo &ci) { return ctx->getBelByLocation(gwu.get_pair_iologic_bel(ctx->getBelLocation(ci.bel))); } bool GowinPacker::is_diff_io(BelId bel) { return ctx->getBoundBelCell(bel)->params.count(id_DIFF_TYPE) != 0; } bool GowinPacker::is_mipi_io(BelId bel) { return ctx->getBoundBelCell(bel)->params.count(id_MIPI_IBUF) || ctx->getBoundBelCell(bel)->params.count(id_MIPI_OBUF); } CellInfo *GowinPacker::create_aux_iologic_cell(CellInfo &ci, IdString mode, bool io16, int idx) { if (ci.type.in(id_ODDR, id_ODDRC, id_OSER4, id_IDDR, id_IDDRC, id_IDES4, id_IOLOGICI_EMPTY, id_IOLOGICO_EMPTY)) { return nullptr; } IdString aux_name = gwu.create_aux_name(ci.name, idx); BelId bel = get_aux_iologic_bel(ci); BelId io_bel = gwu.get_io_bel_from_iologic(bel); if (!ctx->checkBelAvail(io_bel)) { if (!(is_diff_io(io_bel) || is_mipi_io(io_bel))) { log_error("Can't place %s at %s because of a conflict with another IO %s\n", ctx->nameOf(&ci), ctx->nameOfBel(bel), ctx->nameOf(ctx->getBoundBelCell(io_bel))); } } ctx->createCell(aux_name, id_IOLOGIC_DUMMY); CellInfo *aux = ctx->cells.at(aux_name).get(); ci.copyPortTo(id_PCLK, aux, id_PCLK); ci.copyPortTo(id_RESET, aux, id_RESET); if (io16) { aux->setParam(mode, Property("DDRENABLE16")); } else { aux->setParam(mode, Property("DDRENABLE")); } aux->setAttr(ctx->id("IOLOGIC_TYPE"), Property("DUMMY")); aux->setAttr(ctx->id("MAIN_CELL"), Property(ci.name.str(ctx))); ctx->bindBel(bel, aux, PlaceStrength::STRENGTH_LOCKED); return aux; } void GowinPacker::reconnect_ides_outs(CellInfo *ci) { IdString dest_ports[] = {id_Q9, id_Q8, id_Q7, id_Q6, id_Q5, id_Q4, id_Q3, id_Q2}; switch (ci->type.hash()) { case ID_IDDR: /* fall-through*/ case ID_IDDRC: ci->renamePort(id_Q1, id_Q9); ci->renamePort(id_Q0, id_Q8); break; case ID_IDES4: for (int i = 0; i < 4; ++i) { ci->renamePort(ctx->idf("Q%d", 3 - i), dest_ports[i]); } break; case ID_IVIDEO: for (int i = 0; i < 7; ++i) { ci->renamePort(ctx->idf("Q%d", 6 - i), dest_ports[i]); } break; case ID_IDES8: for (int i = 0; i < 8; ++i) { ci->renamePort(ctx->idf("Q%d", 7 - i), dest_ports[i]); } break; default: break; } } void GowinPacker::pack_ides_iol(CellInfo &ci, std::vector &nets_to_remove) { IdString in_port = id_D; CellInfo *in_iob = net_driven_by(ctx, ci.ports.at(in_port).net, is_iob, id_O); NPNR_ASSERT(in_iob != nullptr && in_iob->bel != BelId()); BelId iob_bel = in_iob->bel; BelId l_bel = get_iologici_bel(in_iob); if (l_bel == BelId()) { log_error("Can't place IOLOGIC %s at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel)); } // mark IOB as used by IOLOGIC in_iob->setParam(id_IOLOGIC_IOB, 1); check_iologic_placement(ci, ctx->getBelLocation(iob_bel), in_iob->params.count(id_DIFF_TYPE) || in_iob->params.count(id_MIPI_IBUF)); if (!ctx->checkBelAvail(l_bel)) { log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), ctx->nameOfBel(l_bel), ctx->nameOf(ctx->getBoundBelCell(l_bel))); } ctx->bindBel(l_bel, &ci, PlaceStrength::STRENGTH_LOCKED); std::string in_mode; switch (ci.type.hash()) { case ID_IOLOGICI_EMPTY: in_mode = "EMPTY"; break; case ID_IDDR: case ID_IDDRC: in_mode = "IDDRX1"; break; case ID_IDES4: in_mode = "IDDRX2"; break; case ID_IDES8: in_mode = "IDDRX4"; break; case ID_IDES10: in_mode = "IDDRX5"; break; case ID_IVIDEO: in_mode = "VIDEORX"; break; } ci.setParam(ctx->id("INMODE"), in_mode); // disconnect D input: it is wired internally nets_to_remove.push_back(ci.getPort(in_port)->name); in_iob->disconnectPort(id_O); ci.disconnectPort(in_port); if (ci.type == id_IOLOGICI_EMPTY) { if (ci.attrs.count(id_HAS_REG) == 0) { ci.movePortTo(id_Q, in_iob, id_O); } return; } set_daaj_nets(ci, iob_bel); reconnect_ides_outs(&ci); make_iob_nets(*in_iob); } void GowinPacker::pack_iem(void) { log_info("Pack Input Edge Monitors...\n"); std::vector cells_to_remove; std::vector> new_cells; for (auto &cell : ctx->cells) { CellInfo &ci = *cell.second; if (ci.type != id_IEM) { continue; } if (ctx->debug) { log_info("pack %s of type %s.\n", ctx->nameOf(&ci), ci.type.c_str(ctx)); } // IEM is part of IOLOGIC but functions independently of the // presence/absence of other IOLOGIC components. Therefore, we use // the existing cell whenever possible. const NetInfo *d_net = ci.ports.at(id_D).net; CellInfo *in_iob = net_driven_by(ctx, d_net, is_iob, id_O); NPNR_ASSERT(in_iob != nullptr && in_iob->bel != BelId()); BelId iob_bel = in_iob->bel; BelId l_bel = get_iologici_bel(in_iob); if (l_bel == BelId()) { log_error("Can't place IOLOGIC %s at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel)); } CellInfo *iologic = nullptr; for (auto &usr : d_net->users) { if (is_iologici(usr.cell)) { if (ctx->debug) { log_info(" found IOLOGIC cell %s of type %s, use it.\n", ctx->nameOf(usr.cell), usr.cell->type.c_str(ctx)); } iologic = usr.cell; if (iologic->ports.count(id_CLK)) { NPNR_ASSERT(iologic->ports.at(id_CLK).net == ci.ports.at(id_CLK).net); } else { if (iologic->ports.count(id_PCLK)) { NPNR_ASSERT(iologic->ports.at(id_PCLK).net == ci.ports.at(id_CLK).net); } iologic->addInput(ctx->id("CLK")); } if (iologic->ports.count(id_RESET)) { NPNR_ASSERT(iologic->ports.at(id_RESET).net == ci.ports.at(id_RESET).net); } else { iologic->addInput(ctx->id("RESET")); } break; } } if (iologic == nullptr) { IdString iologic_name = gwu.create_aux_name(ci.name); if (ctx->debug) { log_info(" create IOLOGIC cell %s.\n", iologic_name.c_str(ctx)); } auto iologic_cell = gwu.create_cell(iologic_name, id_IOLOGICI_EMPTY); new_cells.push_back(std::move(iologic_cell)); iologic = new_cells.back().get(); ci.copyPortTo(id_D, iologic, id_D); ci.copyPortTo(id_CLK, iologic, id_CLK); ci.copyPortTo(id_RESET, iologic, id_RESET); } ci.movePortTo(id_MCLK, iologic, id_MCLK); ci.movePortTo(id_LAG, iologic, id_LAG); ci.movePortTo(id_LEAD, iologic, id_LEAD); ci.disconnectPort(id_D); ci.disconnectPort(id_CLK); ci.disconnectPort(id_RESET); // WINSIZE attribute defines routing to ports WINSIZE0/1 iologic->addInput(id_WINSIZE0); iologic->addInput(id_WINSIZE1); if (ci.params.count(id_WINSIZE) == 0) { ci.setParam(id_WINSIZE, Property("SMALL")); } NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get(); NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get(); IdString winsize = ctx->id(ci.params.at(id_WINSIZE).as_string()); switch (winsize.hash()) { case ID_SMALL: iologic->connectPort(id_WINSIZE0, vss_net); iologic->connectPort(id_WINSIZE1, vss_net); break; case ID_MIDSMALL: iologic->connectPort(id_WINSIZE0, vcc_net); iologic->connectPort(id_WINSIZE1, vss_net); break; case ID_MIDLARGE: iologic->connectPort(id_WINSIZE0, vss_net); iologic->connectPort(id_WINSIZE1, vcc_net); break; case ID_LARGE: iologic->connectPort(id_WINSIZE0, vcc_net); iologic->connectPort(id_WINSIZE1, vcc_net); break; default: log_error("%s has incorrect WINSIZE:%s\n", ctx->nameOf(&ci), ci.params.at(id_WINSIZE).c_str()); } if (ci.params.count(id_GSREN) != 0) { if (iologic->params.count(id_GSREN) == 0) { iologic->setParam(id_GSREN, ci.params.at(id_GSREN)); } else { if (ci.params.at(id_GSREN) != iologic->params.at(id_GSREN)) { log_error("GSREN parameter values of %s and %s do not match.\n", ctx->nameOf(&ci), ctx->nameOf(iologic)); } } } if (ci.params.count(id_LSREN) != 0) { if (iologic->params.count(id_LSREN) == 0) { iologic->setParam(id_LSREN, ci.params.at(id_LSREN)); } else { if (ci.params.at(id_LSREN) != iologic->params.at(id_LSREN)) { log_error("LSREN parameter values of %s and %s do not match.\n", ctx->nameOf(&ci), ctx->nameOf(iologic)); } } } cells_to_remove.push_back(ci.name); } for (auto cell : cells_to_remove) { ctx->cells.erase(cell); } for (auto &ncell : new_cells) { ctx->cells[ncell->name] = std::move(ncell); } } void GowinPacker::pack_iodelay(void) { log_info("Pack IODELAY...\n"); std::vector cells_to_remove; std::vector nets_to_remove; std::vector> new_cells; for (auto &cell : ctx->cells) { CellInfo &ci = *cell.second; if (ci.type != id_IODELAY) { continue; } if (ctx->debug) { log_info("pack %s of type %s.\n", ctx->nameOf(&ci), ci.type.c_str(ctx)); } // There is only one delay line in the IO block, which can be either // input or output. Define which case we are dealing with. bool is_idelay = false; NetInfo *di_net = ci.ports.at(id_DI).net; NetInfo *do_net = ci.ports.at(id_DO).net; CellInfo *iob = net_driven_by(ctx, di_net, is_iob, id_O); if (iob != nullptr) { NPNR_ASSERT(iob->bel != BelId()); if (di_net->users.entries() != 1) { log_error("IODELAY %s should be the only sink in the %s network.\n", ctx->nameOf(&ci), ctx->nameOf(di_net)); } is_idelay = true; } else { iob = net_only_drives(ctx, do_net, is_iob, id_I, true); if (iob != nullptr) { NPNR_ASSERT(iob->bel != BelId()); } else { log_error("IODELAY %s is not connected to the pin.\n", ctx->nameOf(&ci)); } } BelId iob_bel = iob->bel; BelId l_bel = get_iologici_bel(iob); if (l_bel == BelId()) { log_error("Can't place IOLOGIC %s at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel)); } // find IOLOGIC connected or create dummy one CellInfo *iologic = nullptr; Property attr; IdString dummy_iol_type; if (is_idelay) { attr = Property("IN"); dummy_iol_type = id_IOLOGICI_EMPTY; for (auto &usr : do_net->users) { if (is_iologici(usr.cell)) { iologic = usr.cell; if (iologic->attrs.count(id_IODELAY) != 0) { log_error("Only one IODELAY allowed per IO block %s.\n", ctx->nameOfBel(iob->bel)); } if (ctx->debug) { log_info(" found IOLOGIC cell %s of type %s, use it.\n", ctx->nameOf(iologic), iologic->type.c_str(ctx)); } } } } else { attr = Property("OUT"); dummy_iol_type = id_IOLOGICO_EMPTY; if (is_iologico(di_net->driver.cell)) { iologic = di_net->driver.cell; if (iologic->attrs.count(id_IODELAY) != 0) { log_error("Only one IODELAY allowed per IO block %s.\n", ctx->nameOfBel(iob->bel)); } if (ctx->debug) { log_info(" found IOLOGIC cell %s of type %s, use it.\n", ctx->nameOf(iologic), iologic->type.c_str(ctx)); } } } if (iologic == nullptr) { IdString iologic_name = gwu.create_aux_name(ci.name); if (ctx->debug) { log_info(" create IOLOGIC cell %s.\n", iologic_name.c_str(ctx)); } auto iologic_cell = gwu.create_cell(iologic_name, dummy_iol_type); new_cells.push_back(std::move(iologic_cell)); iologic = new_cells.back().get(); iologic->addInput(id_D); iologic->addOutput(id_Q); ci.movePortTo(id_DI, iologic, id_D); ci.movePortTo(id_DO, iologic, id_Q); } else { if (is_idelay) { iob->disconnectPort(id_O); ci.disconnectPort(id_I); ci.movePortTo(id_DO, iob, id_O); } else { IdString iol_out = di_net->driver.port; ci.disconnectPort(id_DI); iologic->disconnectPort(iol_out); ci.movePortTo(id_DO, iologic, iol_out); } nets_to_remove.push_back(di_net->name); } ci.movePortTo(id_SDTAP, iologic, id_SDTAP); ci.movePortTo(id_SETN, iologic, id_SETN); ci.movePortTo(id_VALUE, iologic, id_VALUE); ci.movePortTo(id_DF, iologic, id_DF); if (ci.params.count(id_C_STATIC_DLY)) { iologic->setParam(id_C_STATIC_DLY, ci.params.at(id_C_STATIC_DLY)); } iologic->setAttr(id_IODELAY, attr); cells_to_remove.push_back(ci.name); } for (auto cell : cells_to_remove) { ctx->cells.erase(cell); } for (auto &ncell : new_cells) { ctx->cells[ncell->name] = std::move(ncell); } for (auto net : nets_to_remove) { ctx->nets.erase(net); } } void GowinPacker::pack_iologic(void) { log_info("Pack IO logic...\n"); std::vector nets_to_remove; for (auto &cell : ctx->cells) { CellInfo &ci = *cell.second; if (!(is_iologici(&ci) || is_iologico(&ci))) { continue; } if (ctx->debug) { log_info("pack %s of type %s.\n", ctx->nameOf(&ci), ci.type.c_str(ctx)); } if (ci.type.in(id_ODDR, id_ODDRC, id_OSER4, id_OSER8)) { pack_bi_output_iol(ci, nets_to_remove); create_aux_iologic_cell(ci, ctx->id("OUTMODE")); continue; } if (ci.type.in(id_OVIDEO, id_OSER10, id_IOLOGICO_EMPTY)) { pack_single_output_iol(ci, nets_to_remove); create_aux_iologic_cell(ci, ctx->id("OUTMODE")); continue; } if (ci.type.in(id_IDDR, id_IDDRC, id_IDES4, id_IDES8, id_IDES10, id_IVIDEO, id_IOLOGICI_EMPTY)) { pack_ides_iol(ci, nets_to_remove); create_aux_iologic_cell(ci, ctx->id("INMODE")); continue; } } for (auto net : nets_to_remove) { ctx->nets.erase(net); } } // =================================== // IDES16 / OSER16 // =================================== void GowinPacker::check_io16_placement(CellInfo &ci, Loc main_loc, Loc aux_off, int diff /* 1 - diff */) { if (main_loc.z != BelZ::IOBA_Z) { log_error("Can't place %s at %s because OSER16/IDES16 must be placed at A pin\n", ctx->nameOf(&ci), ctx->nameOfBel(ctx->getBelByLocation(main_loc))); } int mod[][3] = {{0, 0, 1}, {1, 1, 0}, {1, 1, 1}}; for (int i = diff; i < 3; ++i) { Loc aux_loc(main_loc.x + mod[i][0] * aux_off.x, main_loc.y + mod[i][1] * aux_off.y, main_loc.z + mod[i][2]); BelId l_bel = ctx->getBelByLocation(aux_loc); if (!ctx->checkBelAvail(l_bel)) { log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), ctx->nameOfBel(l_bel), ctx->nameOf(ctx->getBoundBelCell(l_bel))); } } } void GowinPacker::pack_oser16(CellInfo &ci, std::vector &nets_to_remove) { IdString out_port = id_Q; CellInfo *out_iob = net_only_drives(ctx, ci.ports.at(out_port).net, is_iob, id_I, true); NPNR_ASSERT(out_iob != nullptr && out_iob->bel != BelId()); // mark IOB as used by IOLOGIC out_iob->setParam(id_IOLOGIC_IOB, 1); BelId iob_bel = out_iob->bel; Loc iob_loc = ctx->getBelLocation(iob_bel); Loc aux_offset = gwu.get_tile_io16_offs(iob_loc.x, iob_loc.y); if (aux_offset.x == 0 && aux_offset.y == 0) { log_error("OSER16 %s can not be placed at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel)); } check_io16_placement(ci, iob_loc, aux_offset, out_iob->params.count(id_DIFF_TYPE)); BelId main_bel = ctx->getBelByLocation(Loc(iob_loc.x, iob_loc.y, BelZ::OSER16_Z)); ctx->bindBel(main_bel, &ci, PlaceStrength::STRENGTH_LOCKED); // disconnect Q output: it is wired internally nets_to_remove.push_back(ci.getPort(out_port)->name); out_iob->disconnectPort(id_I); ci.disconnectPort(out_port); // to simplify packaging, the parts of the OSER16 are presented as IOLOGIC cells // and one of these aux cells is declared as main IdString main_name = gwu.create_aux_name(ci.name); IdString aux_name = gwu.create_aux_name(ci.name, 1); ctx->createCell(aux_name, id_IOLOGIC_DUMMY); CellInfo *aux = ctx->cells.at(aux_name).get(); aux->setAttr(ctx->id("MAIN_CELL"), Property(main_name.str(ctx))); aux->setParam(ctx->id("OUTMODE"), Property("ODDRX8")); aux->setParam(ctx->id("UPDATE"), Property("SAME")); aux->setAttr(ctx->id("IOLOGIC_TYPE"), Property("DUMMY")); ci.copyPortTo(id_PCLK, aux, id_PCLK); ci.copyPortTo(id_RESET, aux, id_RESET); ctx->bindBel(ctx->getBelByLocation(Loc(iob_loc.x, iob_loc.y, BelZ::IOLOGICA_Z)), aux, PlaceStrength::STRENGTH_LOCKED); // make aux cell in the first cell aux = create_aux_iologic_cell(*aux, ctx->id("OUTMODE"), true, 2); aux->setAttr(ctx->id("MAIN_CELL"), Property(main_name.str(ctx))); aux->setParam(ctx->id("UPDATE"), Property("SAME")); // make cell in the next location ctx->createCell(main_name, id_IOLOGIC); aux = ctx->cells.at(main_name).get(); aux->setAttr(ctx->id("MAIN_CELL"), Property(main_name.str(ctx))); aux->setParam(ctx->id("OUTMODE"), Property("DDRENABLE16")); aux->setParam(ctx->id("UPDATE"), Property("SAME")); aux->setAttr(ctx->id("IOLOGIC_TYPE"), Property("DUMMY")); ci.copyPortTo(id_PCLK, aux, id_PCLK); ci.copyPortTo(id_RESET, aux, id_RESET); ci.movePortTo(id_FCLK, aux, id_FCLK); ci.movePortTo(id_D12, aux, id_D0); ci.movePortTo(id_D13, aux, id_D1); ci.movePortTo(id_D14, aux, id_D2); ci.movePortTo(id_D15, aux, id_D3); Loc next_io16(iob_loc.x + aux_offset.x, iob_loc.y + aux_offset.y, BelZ::IOLOGICA_Z); ctx->bindBel(ctx->getBelByLocation(next_io16), aux, PlaceStrength::STRENGTH_LOCKED); Loc io_loc = ctx->getBelLocation(iob_bel); if (io_loc.y == ctx->getGridDimY() - 1) { config_bottom_row(*out_iob, io_loc, Bottom_io_POD::DDR); } make_iob_nets(*out_iob); } void GowinPacker::pack_ides16(CellInfo &ci, std::vector &nets_to_remove) { IdString in_port = id_D; CellInfo *in_iob = net_driven_by(ctx, ci.ports.at(in_port).net, is_iob, id_O); NPNR_ASSERT(in_iob != nullptr && in_iob->bel != BelId()); // mark IOB as used by IOLOGIC in_iob->setParam(id_IOLOGIC_IOB, 1); BelId iob_bel = in_iob->bel; Loc iob_loc = ctx->getBelLocation(iob_bel); Loc aux_offset = gwu.get_tile_io16_offs(iob_loc.x, iob_loc.y); if (aux_offset.x == 0 && aux_offset.y == 0) { log_error("IDES16 %s can not be placed at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel)); } check_io16_placement(ci, iob_loc, aux_offset, in_iob->params.count(id_DIFF_TYPE)); BelId main_bel = ctx->getBelByLocation(Loc(iob_loc.x, iob_loc.y, BelZ::IDES16_Z)); ctx->bindBel(main_bel, &ci, PlaceStrength::STRENGTH_LOCKED); // disconnect Q output: it is wired internally nets_to_remove.push_back(ci.getPort(in_port)->name); in_iob->disconnectPort(id_O); ci.disconnectPort(in_port); // to simplify packaging, the parts of the IDES16 are presented as IOLOGIC cells // and one of these aux cells is declared as main IdString main_name = gwu.create_aux_name(ci.name); IdString aux_name = gwu.create_aux_name(ci.name, 1); ctx->createCell(aux_name, id_IOLOGIC_DUMMY); CellInfo *aux = ctx->cells.at(aux_name).get(); aux->setAttr(ctx->id("MAIN_CELL"), Property(main_name.str(ctx))); aux->setParam(ctx->id("INMODE"), Property("IDDRX8")); aux->setAttr(ctx->id("IOLOGIC_TYPE"), Property("DUMMY")); ci.copyPortTo(id_PCLK, aux, id_PCLK); ci.copyPortTo(id_RESET, aux, id_RESET); ctx->bindBel(ctx->getBelByLocation(Loc(iob_loc.x, iob_loc.y, BelZ::IOLOGICA_Z)), aux, PlaceStrength::STRENGTH_LOCKED); // make aux cell in the first cell aux = create_aux_iologic_cell(*aux, ctx->id("INMODE"), true, 2); aux->setAttr(ctx->id("MAIN_CELL"), Property(main_name.str(ctx))); ci.copyPortTo(id_CALIB, aux, id_CALIB); // make cell in the next location ctx->createCell(main_name, id_IOLOGIC); aux = ctx->cells.at(main_name).get(); aux->setAttr(ctx->id("MAIN_CELL"), Property(main_name.str(ctx))); aux->setParam(ctx->id("INMODE"), Property("DDRENABLE16")); aux->setAttr(ctx->id("IOLOGIC_TYPE"), Property("DUMMY")); ci.copyPortTo(id_PCLK, aux, id_PCLK); ci.copyPortTo(id_RESET, aux, id_RESET); ci.copyPortTo(id_CALIB, aux, id_CALIB); ci.movePortTo(id_FCLK, aux, id_FCLK); ci.movePortTo(id_Q0, aux, id_Q6); ci.movePortTo(id_Q1, aux, id_Q7); ci.movePortTo(id_Q2, aux, id_Q8); ci.movePortTo(id_Q3, aux, id_Q9); Loc next_io16(iob_loc.x + aux_offset.x, iob_loc.y + aux_offset.y, BelZ::IOLOGICA_Z); ctx->bindBel(ctx->getBelByLocation(next_io16), aux, PlaceStrength::STRENGTH_LOCKED); make_iob_nets(*in_iob); } void GowinPacker::pack_io16(void) { std::vector nets_to_remove; log_info("Pack DESER16 logic...\n"); for (auto &cell : ctx->cells) { CellInfo &ci = *cell.second; if (ci.type == id_OSER16) { if (ctx->debug) { log_info("pack %s of type %s.\n", ctx->nameOf(&ci), ci.type.c_str(ctx)); } pack_oser16(ci, nets_to_remove); continue; } if (ci.type == id_IDES16) { if (ctx->debug) { log_info("pack %s of type %s.\n", ctx->nameOf(&ci), ci.type.c_str(ctx)); } pack_ides16(ci, nets_to_remove); continue; } } for (auto net : nets_to_remove) { ctx->nets.erase(net); } } NEXTPNR_NAMESPACE_END