nextpnr/himbaechel/uarch/gowin/pack.cc

652 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <map>
#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
// ===================================
// 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<CellInfo>(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<CellInfo>(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<CellInfo>(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<BelId> 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 (lets 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<RegionPlug>(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 (lets 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<RegionPlug>(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<RegionPlug>(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<std::unique_ptr<CellInfo>> 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<CellInfo> 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<std::unique_ptr<CellInfo>> 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