#include #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 // =================================== // Global set/reset // =================================== void GowinPacker::pack_gsr(void) { log_info("Pack GSR...\n"); bool user_gsr = false; for (auto &cell : ctx->cells) { auto &ci = *cell.second; if (ci.type == id_GSR) { user_gsr = true; break; } } if (!user_gsr) { // make default GSR auto gsr_cell = std::make_unique(ctx, id_GSR, id_GSR); gsr_cell->addInput(id_GSRI); gsr_cell->connectPort(id_GSRI, ctx->nets.at(ctx->id("$PACKER_VCC")).get()); ctx->cells[gsr_cell->name] = std::move(gsr_cell); } if (ctx->verbose) { if (user_gsr) { log_info("Have user GSR\n"); } else { log_info("No user GSR. Make one.\n"); } } } // =================================== // Pin function configuration via wires // =================================== void GowinPacker::pack_pincfg(void) { if (!gwu.has_PINCFG()) { return; } log_info("Pack PINCFG...\n"); auto pincfg_cell = std::make_unique(ctx, id_PINCFG, id_PINCFG); const int pin_cnt = gwu.has_I2CCFG() ? 5 : 4; for (int i = 0; i < pin_cnt; ++i) { IdString port = ctx->idf("UNK%d_VCC", i); pincfg_cell->addInput(port); if (i && gwu.need_CFGPINS_INVERSION()) { pincfg_cell->connectPort(port, ctx->nets.at(ctx->id("$PACKER_GND")).get()); } else { pincfg_cell->connectPort(port, ctx->nets.at(ctx->id("$PACKER_VCC")).get()); } } const ArchArgs &args = ctx->args; pincfg_cell->addInput(id_SSPI); if (args.options.count("sspi_as_gpio")) { pincfg_cell->connectPort(id_SSPI, ctx->nets.at(ctx->id("$PACKER_VCC")).get()); pincfg_cell->setParam(id_SSPI, 1); } else { pincfg_cell->connectPort(id_SSPI, ctx->nets.at(ctx->id("$PACKER_GND")).get()); } if (gwu.has_I2CCFG()) { pincfg_cell->addInput(id_I2C); if (args.options.count("i2c_as_gpio")) { pincfg_cell->connectPort(id_I2C, ctx->nets.at(ctx->id("$PACKER_VCC")).get()); pincfg_cell->setParam(id_I2C, 1); } else { pincfg_cell->connectPort(id_I2C, ctx->nets.at(ctx->id("$PACKER_GND")).get()); } } ctx->cells[pincfg_cell->name] = std::move(pincfg_cell); } // =================================== // Global power regulator // =================================== void GowinPacker::pack_bandgap(void) { if (!gwu.has_BANDGAP()) { return; } log_info("Pack BANDGAP...\n"); bool user_bandgap = false; for (auto &cell : ctx->cells) { auto &ci = *cell.second; if (ci.type == id_BANDGAP) { user_bandgap = true; break; } } if (!user_bandgap) { // make default BANDGAP auto bandgap_cell = std::make_unique(ctx, id_BANDGAP, id_BANDGAP); bandgap_cell->addInput(id_BGEN); bandgap_cell->connectPort(id_BGEN, ctx->nets.at(ctx->id("$PACKER_VCC")).get()); ctx->cells[bandgap_cell->name] = std::move(bandgap_cell); } if (ctx->verbose) { if (user_bandgap) { log_info("Have user BANDGAP\n"); } else { log_info("No user BANDGAP. Make one.\n"); } } } // =================================== // Replace INV with LUT // =================================== void GowinPacker::pack_inv(void) { log_info("Pack INV...\n"); for (auto &cell : ctx->cells) { auto &ci = *cell.second; if (ci.type == id_INV) { ci.type = id_LUT4; ci.renamePort(id_O, id_F); ci.renamePort(id_I, id_I3); // use D - it's simple for INIT ci.params[id_INIT] = Property(0x00ff); } } } // =================================== // PLL // =================================== void GowinPacker::pack_pll(void) { log_info("Pack PLL...\n"); pool used_pll_bels; for (auto &cell : ctx->cells) { auto &ci = *cell.second; if (ci.type.in(id_rPLL, id_PLLVR, id_PLLA)) { gwu.remove_brackets(&ci); // If CLKIN is connected to a special pin, then it makes sense // to try to place the PLL so that it uses a direct connection // to this pin. if (ci.bel == BelId()) { NetInfo *ni = ci.getPort(id_CLKIN); if (ni && ni->driver.cell && ni->driver.cell->bel != BelId()) { BelId pll_bel = gwu.get_pll_bel(ni->driver.cell->bel, id_CLKIN_T); if (ctx->debug) { log_info("PLL clkin driver:%s at %s, PLL bel:%s\n", ctx->nameOf(ni->driver.cell), ctx->getBelName(ni->driver.cell->bel).str(ctx).c_str(), pll_bel != BelId() ? ctx->getBelName(pll_bel).str(ctx).c_str() : "NULL"); } if (pll_bel != BelId() && used_pll_bels.count(pll_bel) == 0) { used_pll_bels.insert(pll_bel); ctx->bindBel(pll_bel, &ci, PlaceStrength::STRENGTH_LOCKED); ci.disconnectPort(id_CLKIN); ci.setParam(id_INSEL, std::string("CLKIN0")); } } } } } } // =================================== // ADC // =================================== void GowinPacker::pack_adc(void) { log_info("Pack ADC...\n"); for (auto &cell : ctx->cells) { auto &ci = *cell.second; if (is_adc(&ci)) { gwu.remove_brackets(&ci); } } } // =================================== // HCLK -- CLKDIV and CLKDIV2 for now // =================================== void GowinPacker::pack_hclk(void) { log_info("Pack HCLK cells...\n"); // In the GW5A series, the CLKDIV2 can simultaneously transmit a signal to // both IOLOGIC and CLKDIV. if (gwu.has_5A_HCLK()) { return; } for (auto &cell : ctx->cells) { auto ci = cell.second.get(); if (ci->type != id_CLKDIV) continue; NetInfo *hclk_in = ci->getPort(id_HCLKIN); if (hclk_in) { CellInfo *this_driver = hclk_in->driver.cell; if (this_driver && this_driver->type == id_CLKDIV2) { NetInfo *out = this_driver->getPort(id_CLKOUT); if (out->users.entries() > 1) { // We could do as the IDE does sometimes and replicate the CLKDIV2 cell // as many times as we need. For now, we keep things simple log_error("CLKDIV2 that drives CLKDIV should drive no other cells\n"); } ci->cluster = ci->name; this_driver->cluster = ci->name; ci->constr_children.push_back(this_driver); this_driver->constr_x = 0; this_driver->constr_y = 0; this_driver->constr_z = BelZ::CLKDIV2_0_Z - BelZ::CLKDIV_0_Z; this_driver->constr_abs_z = false; } } } } // =================================== // DLLDLY // =================================== void GowinPacker::pack_dlldly(void) { log_info("Pack DLLDLYs...\n"); for (auto &cell : ctx->cells) { auto ci = cell.second.get(); if (ci->type != id_DLLDLY) continue; NetInfo *clkin_net = ci->getPort(id_CLKIN); NetInfo *clkout_net = ci->getPort(id_CLKOUT); if (clkin_net == nullptr || clkout_net == nullptr) { log_error("%s cell has unconnected CLKIN or CLKOUT pins.\n", ctx->nameOf(ci)); } CellInfo *clk_src = clkin_net->driver.cell; if (!is_io(clk_src)) { log_error("Clock source for DLLDLY %s is not IO: %s.\n", ctx->nameOf(ci), ctx->nameOf(clk_src)); } // DLLDLY placement is fixed BelId io_bel = clk_src->bel; BelId dlldly_bel = gwu.get_dlldly_bel(io_bel); if (dlldly_bel == BelId()) { log_error("Can't use IO %s as input for DLLDLY %s.\n", ctx->nameOf(clk_src), ctx->nameOf(ci)); } if (ctx->verbose) { log_info(" pack %s to use clock pin at %s\n", ctx->nameOf(ci), ctx->nameOfBel(io_bel)); } ctx->bindBel(dlldly_bel, ci, STRENGTH_LOCKED); gwu.remove_brackets(ci); } } // ========================================= // Create entry points to the clock system // ========================================= void GowinPacker::pack_buffered_nets(void) { log_info("Pack buffered nets...\n"); for (auto &net : ctx->nets) { NetInfo *ni = net.second.get(); if (ni->driver.cell == nullptr || ni->users.empty() || net.first == ctx->id("$PACKER_GND") || net.first == ctx->id("$PACKER_VCC")) { continue; } if (ni->attrs.count(id_CLOCK) == 0) { if (ctx->settings.count(id_NO_GP_CLOCK_ROUTING)) { continue; } if (gwu.driver_is_clksrc(ni->driver) || (!gwu.driver_is_io(ni->driver))) { // no need for buffering continue; } // check users for the clock inputs bool has_clock_users = false; for (auto usr : ni->users) { if (usr.port.in(id_CLKIN, id_CLK, id_CLK0, id_CLK1, id_CLK2, id_CLK3, id_CLKFB)) { // Latch gate signals drive CLK pins but are not clocks if (usr.port == id_CLK && usr.cell->attrs.count(id_LATCH)) continue; has_clock_users = true; break; } } if (!has_clock_users) { continue; } if (ctx->verbose) { log_info("Add buffering to a potentially clock network '%s'\n", ctx->nameOf(ni)); } } // make new BUF cell single user for the net driver IdString buf_name = ctx->idf("%s_BUFG", net.first.c_str(ctx)); ctx->createCell(buf_name, id_BUFG); CellInfo *buf_ci = ctx->cells.at(buf_name).get(); buf_ci->addInput(id_I); // move driver CellInfo *driver_cell = ni->driver.cell; IdString driver_port = ni->driver.port; driver_cell->movePortTo(driver_port, buf_ci, id_O); buf_ci->connectPorts(id_I, driver_cell, driver_port); } } // ========================================= // Create DQCEs // ========================================= void GowinPacker::pack_dqce(void) { log_info("Pack DQCE cells...\n"); // At the placement stage, nothing can be said definitively about DQCE, // so we make user cells virtual but allocate all available bels by // creating and placing cells - we will use some of them after, and // delete the rest. // We do this here because the decision about which physical DQCEs to // use is made during routing, but some of the information (let’s say // mapping cell pins -> bel pins) is filled in before routing. bool grab_bels = false; for (auto &cell : ctx->cells) { auto &ci = *cell.second; if (ci.type == id_DQCE) { ci.pseudo_cell = std::make_unique(Loc(0, 0, 0)); grab_bels = true; } } if (grab_bels) { for (int i = 0; i < 32; ++i) { BelId dqce_bel = gwu.get_dqce_bel(ctx->idf("SPINE%d", i)); if (dqce_bel != BelId()) { IdString dqce_name = ctx->idf("$PACKER_DQCE_SPINE%d", i); CellInfo *dqce = ctx->createCell(dqce_name, id_DQCE); dqce->addInput(id_CE); ctx->bindBel(dqce_bel, dqce, STRENGTH_LOCKED); } } } } // ========================================= // Create DCSs // ========================================= void GowinPacker::pack_dcs(void) { log_info("Pack DCS cells...\n"); // At the placement stage, nothing can be said definitively about DCS, // so we make user cells virtual but allocate all available bels by // creating and placing cells - we will use some of them after, and // delete the rest. // We do this here because the decision about which physical DCEs to // use is made during routing, but some of the information (let’s say // mapping cell pins -> bel pins) is filled in before routing. bool grab_bels = false; for (auto &cell : ctx->cells) { auto &ci = *cell.second; if (ci.type == id_DCS) { ci.pseudo_cell = std::make_unique(Loc(0, 0, 0)); grab_bels = true; } } if (grab_bels) { for (int i = 0; i < 8; ++i) { BelId dcs_bel = gwu.get_dcs_bel(ctx->idf("P%d%dA", 1 + (i % 4), 6 + (i >> 2))); if (dcs_bel != BelId()) { IdString dcs_name = ctx->idf("$PACKER_DCS_SPINE%d", 8 * (i % 4) + 6 + (i >> 2)); CellInfo *dcs = ctx->createCell(dcs_name, id_DCS); ctx->copyBelPorts(dcs_name, dcs_bel); ctx->bindBel(dcs_bel, dcs, STRENGTH_LOCKED); } } } } // ========================================= // Create DHCENs // ========================================= void GowinPacker::pack_dhcens(void) { // Allocate all available dhcen bels; we will find out which of them // will actually be used during the routing process. bool grab_bels = false; for (auto &cell : ctx->cells) { auto &ci = *cell.second; if (ci.type == id_DHCEN) { ci.pseudo_cell = std::make_unique(Loc(0, 0, 0)); grab_bels = true; } } if (grab_bels) { // sane message if new primitives are used with old bases auto buckets = ctx->getBelBuckets(); NPNR_ASSERT_MSG(std::find(buckets.begin(), buckets.end(), id_DHCEN) != buckets.end(), "There are no DHCEN bels to use."); int i = 0; for (auto &bel : ctx->getBelsInBucket(ctx->getBelBucketForCellType(id_DHCEN))) { IdString dhcen_name = ctx->idf("$PACKER_DHCEN_%d", ++i); CellInfo *dhcen = ctx->createCell(dhcen_name, id_DHCEN); dhcen->addInput(id_CE); ctx->bindBel(bel, dhcen, STRENGTH_LOCKED); } } } // ========================================= // Enable UserFlash // ========================================= void GowinPacker::pack_userflash(bool have_emcu) { log_info("Pack UserFlash cells...\n"); std::vector> new_cells; for (auto &cell : ctx->cells) { auto &ci = *cell.second; if (!is_userflash(&ci)) { continue; } if (ci.type.in(id_FLASH96K, id_FLASH256K, id_FLASH608K)) { // enable ci.addInput(id_INUSEN); ci.connectPort(id_INUSEN, ctx->nets.at(ctx->id("$PACKER_GND")).get()); } gwu.remove_brackets(&ci); if (have_emcu) { continue; } // add invertor int lut_idx = 0; auto add_inv = [&](IdString port, PortType port_type) { if (!gwu.port_used(&ci, port)) { return; } std::unique_ptr lut_cell = gwu.create_cell(gwu.create_aux_name(ci.name, lut_idx, "_lut$"), id_LUT4); new_cells.push_back(std::move(lut_cell)); CellInfo *lut = new_cells.back().get(); lut->addInput(id_I0); lut->addOutput(id_F); lut->setParam(id_INIT, 0x5555); ++lut_idx; if (port_type == PORT_IN) { ci.movePortTo(port, lut, id_I0); lut->connectPorts(id_F, &ci, port); } else { ci.movePortTo(port, lut, id_F); ci.connectPorts(port, lut, id_I0); } }; for (auto pin : ci.ports) { if (pin.second.type == PORT_OUT) { add_inv(pin.first, PORT_OUT); } else { if (pin.first == id_INUSEN) { continue; } if (ci.type == id_FLASH608K && pin.first.in(id_XADR0, id_XADR1, id_XADR2, id_XADR3, id_XADR4, id_XADR5, id_XADR6, id_XADR7, id_XADR8)) { continue; } add_inv(pin.first, PORT_IN); } } } for (auto &ncell : new_cells) { ctx->cells[ncell->name] = std::move(ncell); } } // ========================================= // Create EMCU // ========================================= void GowinPacker::pack_emcu_and_flash(void) { log_info("Pack EMCU and UserFlash cells...\n"); std::vector> new_cells; bool have_emcu = false; for (auto &cell : ctx->cells) { auto &ci = *cell.second; if (!is_emcu(&ci)) { continue; } have_emcu = true; gwu.remove_brackets(&ci); // The flash data bus is connected directly to the CPU so just disconnect these networks // also other non-switched networks ci.disconnectPort(ctx->id("DAPNTDOEN")); ci.disconnectPort(ctx->id("DAPNTRST")); ci.disconnectPort(ctx->id("DAPTDO")); ci.disconnectPort(ctx->id("DAPTDI")); ci.disconnectPort(ctx->id("TARGFLASH0HREADYMUX")); ci.disconnectPort(ctx->id("TARGEXP0HAUSER")); ci.disconnectPort(ctx->id("TARGFLASH0EXRESP")); ci.disconnectPort(ctx->id("PORESETN")); ci.disconnectPort(ctx->id("SYSRESETN")); ci.disconnectPort(ctx->id("DAPSWDITMS")); ci.disconnectPort(ctx->id("DAPSWCLKTCK")); ci.disconnectPort(ctx->id("TPIUTRACECLK")); for (int i = 0; i < 32; ++i) { if (i < 4) { if (i < 3) { ci.disconnectPort(ctx->idf("TARGFLASH0HSIZE%d", i)); ci.disconnectPort(ctx->idf("TARGFLASH0HBURST%d", i)); ci.disconnectPort(ctx->idf("TARGFLASH0HRUSER%d", i)); ci.disconnectPort(ctx->idf("INITEXP0HRUSER%d", i)); } // ci.disconnectPort(ctx->idf("TARGFLASH0HPROT%d", i)); ci.disconnectPort(ctx->idf("TARGEXP0HWUSER%d", i)); ci.disconnectPort(ctx->idf("MTXREMAP%d", i)); } // ins ci.disconnectPort(ctx->idf("TARGFLASH0HRDATA%d", i)); } } pack_userflash(have_emcu); } void GowinPacker::run(void) { handle_constants(); pack_iobs(); ctx->check(); pack_i3c(); ctx->check(); pack_mipi(); ctx->check(); pack_diff_iobs(); ctx->check(); pack_io_regs(); ctx->check(); pack_iodelay(); ctx->check(); pack_iem(); ctx->check(); pack_iologic(); ctx->check(); pack_io16(); ctx->check(); pack_gsr(); ctx->check(); pack_pincfg(); ctx->check(); pack_hclk(); ctx->check(); pack_dlldly(); ctx->check(); pack_bandgap(); ctx->check(); pack_wideluts(); ctx->check(); pack_alus(); ctx->check(); pack_ssram(); ctx->check(); pack_latches(); ctx->check(); constrain_lutffs(); ctx->check(); pack_pll(); ctx->check(); pack_adc(); ctx->check(); pack_bsram(); ctx->check(); pack_dsp(); ctx->check(); pack_inv(); ctx->check(); pack_buffered_nets(); ctx->check(); pack_emcu_and_flash(); ctx->check(); pack_dhcens(); ctx->check(); pack_dqce(); ctx->check(); pack_dcs(); ctx->check(); ctx->fixupHierarchy(); ctx->check(); } void gowin_pack(Context *ctx) { GowinPacker packer(ctx); packer.run(); } NEXTPNR_NAMESPACE_END