/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Claire Xenia Wolf * Copyright (C) 2020 Pepijn de Vos * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include #include #include #include #include #include "design_utils.h" #include "embed.h" #include "gfx.h" #include "nextpnr.h" #include "placer1.h" #include "placer_heap.h" #include "router1.h" #include "router2.h" #include "util.h" NEXTPNR_NAMESPACE_BEGIN const PairPOD *pairLookup(const PairPOD *list, const size_t len, const int dest); // GUI void Arch::fixClockSpineDecals(void) { for (auto sp : clockSpinesCache) { // row: (#of spine cells, wire_id) dict> rows; IdString min_x, max_x; min_x = max_x = *sp.second.begin(); for (auto wire : sp.second) { WireInfo &wi = wire_info(wire); std::pair &row = rows[wi.y]; ++row.first; row.second = wire; if (wi.x < wire_info(min_x).x) { min_x = wire; } else { if (wi.x > wire_info(max_x).x) { max_x = wire; } } } // central mux row owns the global decal int mux_row = -1; for (auto row : rows) { if (row.second.first == 1) { mux_row = row.first; break; } } // if there is no separate central mux than all decals are the same if (mux_row == -1) { mux_row = rows.begin()->first; WireInfo &wi = wire_info(rows.at(mux_row).second); GraphicElement &el_active = decal_graphics.at(wi.decalxy_active.decal).at(0); GraphicElement &el_inactive = decal_graphics.at(wi.decalxy_inactive.decal).at(0); el_active.y1 -= wi.y; el_active.y2 -= wi.y; el_inactive.y1 -= wi.y; el_inactive.y2 -= wi.y; el_active.x1 += wire_info(min_x).x; el_active.x2 += wire_info(max_x).x; el_inactive.x1 += wire_info(min_x).x; el_inactive.x2 += wire_info(max_x).x; } else { // change the global decal WireInfo &wi = wire_info(rows.at(mux_row).second); // clear spine decals float y = 0.; for (auto wire : sp.second) { if (wire == wi.name) { continue; } wire_info(wire).decalxy_active = DecalXY(); wire_info(wire).decalxy_inactive = DecalXY(); y = wire_info(wire).y; } GraphicElement &el_active = decal_graphics.at(wi.decalxy_active.decal).at(0); GraphicElement &el_inactive = decal_graphics.at(wi.decalxy_inactive.decal).at(0); el_active.y1 -= y; el_active.y2 -= y; el_inactive.y1 -= y; el_inactive.y2 -= y; el_active.x1 += wire_info(min_x).x; el_active.x2 += wire_info(max_x).x; el_inactive.x1 += wire_info(min_x).x; el_inactive.x2 += wire_info(max_x).x; } refreshUi(); } } void Arch::updateClockSpinesCache(IdString spine_id, IdString wire_id) { std::vector &sp = clockSpinesCache[spine_id]; if (std::find(sp.begin(), sp.end(), wire_id) == sp.end()) { sp.push_back(wire_id); } } DecalXY Arch::getBelDecal(BelId bel) const { CellInfo *ci = getBoundBelCell(bel); if (ci == nullptr) { return bels.at(bel).decalxy_inactive; } else { // LUT + used/unused DFF if (bels.at(bel).type == id_SLICE) { DecalXY decalxy = bels.at(bel).decalxy_active; if (!ci->params.at(id_FF_USED).as_bool()) { decalxy.decal = id_DECAL_LUT_UNUSED_DFF_ACTIVE; if (ci->params.count(id_ALU_MODE) != 0) { decalxy.decal = id_DECAL_ALU_ACTIVE; } } return decalxy; } } return bels.at(bel).decalxy_active; } DecalXY Arch::getGroupDecal(GroupId grp) const { return groups.at(grp).decalxy; } DecalXY Arch::getPipDecal(PipId pip) const { if (getBoundPipNet(pip) == nullptr) { return pips.at(pip).decalxy_inactive; } return pips.at(pip).decalxy_active; } DecalXY Arch::getWireDecal(WireId wire) const { static std::vector clk_wires = {id_GB00, id_GB10, id_GB20, id_GB30, id_GB40, id_GB50, id_GB60, id_GB70}; static std::vector pip_dst = {id_CLK0, id_CLK1, id_CLK2, id_EW10, id_EW20, id_SN10, id_SN20}; if (getBoundWireNet(wire) == nullptr) { if (std::find(clk_wires.begin(), clk_wires.end(), wires.at(wire).type) != clk_wires.end()) { for (auto dst : pip_dst) { // check if pip is used char pip_name[20]; snprintf(pip_name, sizeof(pip_name), "%s_%s", wire.c_str(this), dst.c_str(this)); if (pips.count(id(pip_name)) != 0) { if (getBoundPipNet(id(pip_name)) != nullptr) { return wires.at(wire).decalxy_active; } } } } else { // spines if (clockSpinesCache.count(wires.at(wire).type) != 0) { std::vector const &sp = clockSpinesCache.at(wires.at(wire).type); for (auto w : sp) { if (getBoundWireNet(w) != nullptr) { return wires.at(wire).decalxy_active; } } } } return wires.at(wire).decalxy_inactive; } return wires.at(wire).decalxy_active; } bool Arch::allocate_longwire(NetInfo *ni, int lw_idx) { NPNR_ASSERT(ni != nullptr); if (ni->driver.cell == nullptr) { return false; } if (ni->name == id("$PACKER_VCC_NET") || ni->name == id("$PACKER_GND_NET")) { return false; } // So far only for OBUF switch (ni->driver.cell->type.index) { case ID_ODDR: /* fall-through*/ case ID_ODDRC: /* fall-through*/ case ID_IOBUF: /* fall-through*/ case ID_TBUF: return false; case ID_OBUF: if (getCtx()->debug) { log_info("Long wire for IO %s\n", nameOf(ni)); } ni = ni->driver.cell->ports.at(id_I).net; return allocate_longwire(ni, lw_idx); break; default: break; } if (getCtx()->debug) { log_info("Requested index:%d\n", lw_idx); } if (avail_longwires == 0 || (lw_idx != -1 && (avail_longwires & (1 << lw_idx)) == 0)) { return false; } int longwire = lw_idx; if (lw_idx == -1) { for (longwire = 7; longwire >= 0; --longwire) { if (avail_longwires & (1 << longwire)) { break; } } } avail_longwires &= ~(1 << longwire); // BUFS cell CellInfo *bufs; char buf[40]; snprintf(buf, sizeof(buf), "$PACKER_BUFS%d", longwire); std::unique_ptr new_cell = create_generic_cell(getCtx(), id_BUFS, buf); bufs = new_cell.get(); cells[bufs->name] = std::move(new_cell); if (lw_idx != -1) { bufs->cluster = bufs->name; bufs->constr_z = lw_idx + BelZ::bufs_0_z; bufs->constr_abs_z = true; bufs->constr_children.clear(); } // old driver -> bufs LW input net auto net = std::make_unique(idf("$PACKER_BUFS_%c", longwire + 'A')); NetInfo *bufs_net = net.get(); nets[net->name] = std::move(net); // split the net CellInfo *driver_cell = ni->driver.cell; IdString driver_port = ni->driver.port; driver_cell->disconnectPort(driver_port); bufs->connectPort(id_O, ni); bufs->connectPort(id_I, bufs_net); driver_cell->connectPort(driver_port, bufs_net); if (getCtx()->debug) { log_info("Long wire %d was allocated\n", longwire); } return true; } void Arch::auto_longwires() {} void Arch::fix_longwire_bels() { // After routing, it is clear which wires and in which bus SS00 and SS40 are used and // in which quadrant they are routed. Here we write it in the attributes. for (auto &cell : cells) { CellInfo *ci = cell.second.get(); if (ci->type != id_BUFS) { continue; } const NetInfo *ni = ci->getPort(id_O); if (ni == nullptr) { continue; } // bus wire is one of the wires // value does not matter, but the L/R parameter itself for (auto &wire : ni->wires) { WireId w = wires[wire.first].type; switch (w.hash()) { case ID_LWSPINETL0: case ID_LWSPINETL1: case ID_LWSPINETL2: case ID_LWSPINETL3: case ID_LWSPINETL4: case ID_LWSPINETL5: case ID_LWSPINETL6: case ID_LWSPINETL7: case ID_LWSPINEBL0: case ID_LWSPINEBL1: case ID_LWSPINEBL2: case ID_LWSPINEBL3: case ID_LWSPINEBL4: case ID_LWSPINEBL5: case ID_LWSPINEBL6: case ID_LWSPINEBL7: ci->setParam(id("L"), Property(w.str(this))); break; case ID_LWSPINETR0: case ID_LWSPINETR1: case ID_LWSPINETR2: case ID_LWSPINETR3: case ID_LWSPINETR4: case ID_LWSPINETR5: case ID_LWSPINETR6: case ID_LWSPINETR7: case ID_LWSPINEBR0: case ID_LWSPINEBR1: case ID_LWSPINEBR2: case ID_LWSPINEBR3: case ID_LWSPINEBR4: case ID_LWSPINEBR5: case ID_LWSPINEBR6: case ID_LWSPINEBR7: ci->setParam(id("R"), Property(w.str(this))); break; default: break; } } } } WireInfo &Arch::wire_info(IdString wire) { auto w = wires.find(wire); if (w == wires.end()) NPNR_ASSERT_FALSE_STR("no wire named " + wire.str(this)); return w->second; } PipInfo &Arch::pip_info(IdString pip) { auto p = pips.find(pip); if (p == pips.end()) NPNR_ASSERT_FALSE_STR("no pip named " + pip.str(this)); return p->second; } BelInfo &Arch::bel_info(IdString bel) { auto b = bels.find(bel); if (b == bels.end()) NPNR_ASSERT_FALSE_STR("no bel named " + bel.str(this)); return b->second; } NetInfo &Arch::net_info(IdString net) { auto b = nets.find(net); if (b == nets.end()) NPNR_ASSERT_FALSE_STR("no net named " + net.str(this)); return *b->second; } void Arch::addWire(IdString name, IdString type, int x, int y) { NPNR_ASSERT(wires.count(name) == 0); WireInfo &wi = wires[name]; wi.name = name; wi.type = type; wi.x = x; wi.y = y; wire_ids.push_back(name); // Needed to ensure empty tile bel locations if (int(bels_by_tile.size()) <= x) bels_by_tile.resize(x + 1); if (int(bels_by_tile[x].size()) <= y) bels_by_tile[x].resize(y + 1); if (int(tileBelDimZ.size()) <= x) tileBelDimZ.resize(x + 1); if (int(tileBelDimZ[x].size()) <= y) tileBelDimZ[x].resize(y + 1); } void Arch::addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayQuad delay, Loc loc) { NPNR_ASSERT(pips.count(name) == 0); PipInfo &pi = pips[name]; pi.name = name; pi.type = type; pi.srcWire = srcWire; pi.dstWire = dstWire; pi.delay = delay; pi.loc = loc; // log_info("addpip %s->%s %.6f | %s name:%s\n" , srcWire.c_str(this), dstWire.c_str(this), // getDelayNS(delay.maxDelay()), srcWire.c_str(this), name.c_str(this)); wire_info(srcWire).downhill.push_back(name); wire_info(dstWire).uphill.push_back(name); pip_ids.push_back(name); if (int(tilePipDimZ.size()) <= loc.x) tilePipDimZ.resize(loc.x + 1); if (int(tilePipDimZ[loc.x].size()) <= loc.y) tilePipDimZ[loc.x].resize(loc.y + 1); // Needed to ensure empty tile bel locations if (int(bels_by_tile.size()) <= loc.x) bels_by_tile.resize(loc.x + 1); if (int(bels_by_tile[loc.x].size()) <= loc.y) bels_by_tile[loc.x].resize(loc.y + 1); if (int(tileBelDimZ.size()) <= loc.x) tileBelDimZ.resize(loc.x + 1); if (int(tileBelDimZ[loc.x].size()) <= loc.y) tileBelDimZ[loc.x].resize(loc.y + 1); gridDimX = std::max(gridDimX, loc.x + 1); gridDimY = std::max(gridDimY, loc.y + 1); tilePipDimZ[loc.x][loc.y] = std::max(tilePipDimZ[loc.x][loc.y], loc.z + 1); } void Arch::addGroup(IdString name) { NPNR_ASSERT(groups.count(name) == 0); GroupInfo &gi = groups[name]; gi.name = name; } void Arch::addBel(IdString name, IdString type, Loc loc, bool gb) { NPNR_ASSERT(bels.count(name) == 0); NPNR_ASSERT(bel_by_loc.count(loc) == 0); BelInfo &bi = bels[name]; bi.name = name; bi.type = type; bi.x = loc.x; bi.y = loc.y; bi.z = loc.z; bi.gb = gb; bel_ids.push_back(name); bel_by_loc[loc] = name; if (int(bels_by_tile.size()) <= loc.x) bels_by_tile.resize(loc.x + 1); if (int(bels_by_tile[loc.x].size()) <= loc.y) bels_by_tile[loc.x].resize(loc.y + 1); bels_by_tile[loc.x][loc.y].push_back(name); if (int(tileBelDimZ.size()) <= loc.x) tileBelDimZ.resize(loc.x + 1); if (int(tileBelDimZ[loc.x].size()) <= loc.y) tileBelDimZ[loc.x].resize(loc.y + 1); gridDimX = std::max(gridDimX, loc.x + 1); gridDimY = std::max(gridDimY, loc.y + 1); tileBelDimZ[loc.x][loc.y] = std::max(tileBelDimZ[loc.x][loc.y], loc.z + 1); } void Arch::addBelInput(IdString bel, IdString name, IdString wire) { NPNR_ASSERT(bel_info(bel).pins.count(name) == 0); PinInfo &pi = bel_info(bel).pins[name]; pi.name = name; pi.wire = wire; pi.type = PORT_IN; wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name}); wire_info(wire).bel_pins.push_back(BelPin{bel, name}); } void Arch::addBelOutput(IdString bel, IdString name, IdString wire) { NPNR_ASSERT(bel_info(bel).pins.count(name) == 0); PinInfo &pi = bel_info(bel).pins[name]; pi.name = name; pi.wire = wire; pi.type = PORT_OUT; wire_info(wire).uphill_bel_pin = BelPin{bel, name}; wire_info(wire).bel_pins.push_back(BelPin{bel, name}); } void Arch::addBelInout(IdString bel, IdString name, IdString wire) { NPNR_ASSERT(bel_info(bel).pins.count(name) == 0); PinInfo &pi = bel_info(bel).pins[name]; pi.name = name; pi.wire = wire; pi.type = PORT_INOUT; wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name}); wire_info(wire).bel_pins.push_back(BelPin{bel, name}); } void Arch::addGroupBel(IdString group, IdString bel) { groups[group].bels.push_back(bel); } void Arch::addGroupWire(IdString group, IdString wire) { groups[group].wires.push_back(wire); } void Arch::addGroupPip(IdString group, IdString pip) { groups[group].pips.push_back(pip); } void Arch::addGroupGroup(IdString group, IdString grp) { groups[group].groups.push_back(grp); } void Arch::addDecalGraphic(DecalId decal, const GraphicElement &graphic) { decal_graphics[decal].push_back(graphic); refreshUi(); } void Arch::setWireDecal(WireId wire, DecalXY active, DecalXY inactive) { wire_info(wire).decalxy_active = active; wire_info(wire).decalxy_inactive = inactive; refreshUiWire(wire); } void Arch::setPipDecal(PipId pip, DecalXY active, DecalXY inactive) { pip_info(pip).decalxy_active = active; pip_info(pip).decalxy_inactive = inactive; refreshUiPip(pip); } void Arch::setBelDecal(BelId bel, DecalXY active, DecalXY inactive) { bel_info(bel).decalxy_active = active; bel_info(bel).decalxy_inactive = inactive; refreshUiBel(bel); } void Arch::setDefaultDecals(void) { #ifndef NO_GUI for (BelId bel : getBels()) { gfxSetBelDefaultDecal(this, bel_info(bel)); } for (PipId pip : getPips()) { gfxSetPipDefaultDecal(this, pip_info(pip)); } for (WireId wire : getWires()) { gfxSetWireDefaultDecal(this, wire_info(wire)); } fixClockSpineDecals(); #endif } void Arch::setGroupDecal(GroupId group, DecalXY decalxy) { groups[group].decalxy = decalxy; refreshUiGroup(group); } void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) { wire_info(wire).attrs[key] = value; } void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pip_info(pip).attrs[key] = value; } void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bel_info(bel).attrs[key] = value; } void Arch::setDelayScaling(double scale, double offset) { args.delayScale = scale; args.delayOffset = offset; } void Arch::addCellTimingClass(IdString cell, IdString port, TimingPortClass cls) { cellTiming[cell].portClasses[port] = cls; } void Arch::addCellTimingClock(IdString cell, IdString port) { cellTiming[cell].portClasses[port] = TMG_CLOCK_INPUT; } void Arch::addCellTimingDelay(IdString cell, IdString fromPort, IdString toPort, DelayQuad delay) { if (get_or_default(cellTiming[cell].portClasses, fromPort, TMG_IGNORE) == TMG_IGNORE) cellTiming[cell].portClasses[fromPort] = TMG_COMB_INPUT; if (get_or_default(cellTiming[cell].portClasses, toPort, TMG_IGNORE) == TMG_IGNORE) cellTiming[cell].portClasses[toPort] = TMG_COMB_OUTPUT; cellTiming[cell].combDelays[CellDelayKey{fromPort, toPort}] = delay; } void Arch::addCellTimingSetupHold(IdString cell, IdString port, IdString clock, DelayPair setup, DelayPair hold) { TimingClockingInfo ci; ci.clock_port = clock; ci.edge = RISING_EDGE; ci.setup = setup; ci.hold = hold; cellTiming[cell].clockingInfo[port].push_back(ci); cellTiming[cell].portClasses[port] = TMG_REGISTER_INPUT; } void Arch::addCellTimingClockToOut(IdString cell, IdString port, IdString clock, DelayQuad clktoq) { TimingClockingInfo ci; ci.clock_port = clock; ci.edge = RISING_EDGE; ci.clockToQ = clktoq; cellTiming[cell].clockingInfo[port].push_back(ci); cellTiming[cell].portClasses[port] = TMG_REGISTER_OUTPUT; } // --------------------------------------------------------------- // TODO represent wires more intelligently. IdString Arch::wireToGlobal(int &row, int &col, const DatabasePOD *db, IdString &wire) { const std::string &wirename = wire.str(this); char buf[32]; if (wirename == "VCC" || wirename == "VSS") { row = 0; col = 0; return wire; } if (!isdigit(wirename[1]) || !isdigit(wirename[2]) || !isdigit(wirename[3])) { snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, wirename.c_str()); return id(buf); } char direction = wirename[0]; int num = std::stoi(wirename.substr(1, 2)); int segment = std::stoi(wirename.substr(3, 1)); switch (direction) { case 'N': row += segment; break; case 'S': row -= segment; break; case 'E': col -= segment; break; case 'W': col += segment; break; default: snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, wirename.c_str()); return id(buf); break; } // wires wrap around the edges // assumes 0-based indexes if (row < 0) { row = -1 - row; direction = 'N'; } else if (col < 0) { col = -1 - col; direction = 'W'; } else if (row >= db->rows) { row = 2 * db->rows - 1 - row; direction = 'S'; } else if (col >= db->cols) { col = 2 * db->cols - 1 - col; direction = 'E'; } snprintf(buf, 32, "%c%d0", direction, num); wire = id(buf); // local aliases const TilePOD *tile = db->grid[row * db->cols + col].get(); auto local_alias = pairLookup(tile->aliases.get(), tile->num_aliases, wire.index); if (local_alias != nullptr) { wire = IdString(local_alias->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, wire.c_str(this)); } else { snprintf(buf, 32, "R%dC%d_%c%d", row + 1, col + 1, direction, num); } return id(buf); } const PairPOD *pairLookup(const PairPOD *list, const size_t len, const int dest) { for (size_t i = 0; i < len; i++) { const PairPOD *pair = &list[i]; if (pair->dest_id == dest) { return pair; } } return nullptr; } const PinPOD *pinLookup(const PinPOD *list, const size_t len, const int idx) { for (size_t i = 0; i < len; i++) { const PinPOD *pin = &list[i]; if (pin->index_id == idx) { return pin; } } return nullptr; } bool aliasCompare(GlobalAliasPOD i, GlobalAliasPOD j) { return (i.dest_row < j.dest_row) || (i.dest_row == j.dest_row && i.dest_col < j.dest_col) || (i.dest_row == j.dest_row && i.dest_col == j.dest_col && i.dest_id < j.dest_id); } bool timingCompare(TimingPOD i, TimingPOD j) { return i.name_id < j.name_id; } template const T *genericLookup(const T *first, int len, const T val, C compare) { auto res = std::lower_bound(first, first + len, val, compare); if (res - first != len && !compare(val, *res)) { return res; } else { return nullptr; } } template const T *timingLookup(const T *first, int len, const T val, C compare) { for (int i = 0; i < len; ++i) { auto res = &first[i]; if (!(compare(*res, val) || compare(val, *res))) { return res; } } return nullptr; } DelayQuad delayLookup(const TimingPOD *first, int len, IdString name) { TimingPOD needle; needle.name_id = name.index; const TimingPOD *timing = timingLookup(first, len, needle, timingCompare); DelayQuad delay; if (timing != nullptr) { delay.fall.max_delay = std::max(timing->ff, timing->rf) / 1000.; delay.fall.min_delay = std::min(timing->ff, timing->rf) / 1000.; delay.rise.max_delay = std::max(timing->rr, timing->fr) / 1000.; delay.rise.min_delay = std::min(timing->rr, timing->fr) / 1000.; } else { delay = DelayQuad(0); } return delay; } DelayQuad Arch::getWireTypeDelay(IdString wire) { IdString len; IdString glbsrc; switch (wire.index) { case ID_X01: case ID_X02: case ID_X03: case ID_X04: case ID_X05: case ID_X06: case ID_X07: case ID_X08: case ID_I0: case ID_I1: len = id_X0; break; case ID_N100: case ID_N130: case ID_S100: case ID_S130: case ID_E100: case ID_E130: case ID_W100: case ID_W130: case ID_E110: case ID_W110: case ID_E120: case ID_W120: case ID_S110: case ID_N110: case ID_S120: case ID_N120: case ID_SN10: case ID_SN20: case ID_EW10: case ID_EW20: case ID_I01: len = id_FX1; break; case ID_N200: case ID_N210: case ID_N220: case ID_N230: case ID_N240: case ID_N250: case ID_N260: case ID_N270: case ID_S200: case ID_S210: case ID_S220: case ID_S230: case ID_S240: case ID_S250: case ID_S260: case ID_S270: case ID_E200: case ID_E210: case ID_E220: case ID_E230: case ID_E240: case ID_E250: case ID_E260: case ID_E270: case ID_W200: case ID_W210: case ID_W220: case ID_W230: case ID_W240: case ID_W250: case ID_W260: case ID_W270: len = id_X2; break; case ID_N800: case ID_N810: case ID_N820: case ID_N830: case ID_S800: case ID_S810: case ID_S820: case ID_S830: case ID_E800: case ID_E810: case ID_E820: case ID_E830: case ID_W800: case ID_W810: case ID_W820: case ID_W830: len = id_X8; break; case ID_LT02: case ID_LT13: glbsrc = id_SPINE_TAP_SCLK_0; break; case ID_LT01: case ID_LT04: glbsrc = id_SPINE_TAP_SCLK_1; break; case ID_LBO0: case ID_LBO1: glbsrc = id_TAP_BRANCH_SCLK; break; case ID_LB01: case ID_LB11: case ID_LB21: case ID_LB31: case ID_LB41: case ID_LB51: case ID_LB61: case ID_LB71: glbsrc = id_BRANCH_SCLK; break; case ID_GT00: case ID_GT10: glbsrc = id_SPINE_TAP_PCLK; break; case ID_GBO0: case ID_GBO1: glbsrc = id_TAP_BRANCH_PCLK; break; case ID_GB00: case ID_GB10: case ID_GB20: case ID_GB30: case ID_GB40: case ID_GB50: case ID_GB60: case ID_GB70: glbsrc = id_BRANCH_PCLK; break; default: if (wire.str(this).rfind("LWSPINE", 0) == 0) { glbsrc = IdString(ID_CENT_SPINE_SCLK); } else if (wire.str(this).rfind("SPINE", 0) == 0) { glbsrc = IdString(ID_CENT_SPINE_PCLK); } else if (wire.str(this).rfind("UNK", 0) == 0) { glbsrc = IdString(ID_PIO_CENT_PCLK); } break; } if (len != IdString()) { return delayLookup(speed->wire.timings.get(), speed->wire.num_timings, len); } else if (glbsrc != IdString()) { return delayLookup(speed->glbsrc.timings.get(), speed->glbsrc.num_timings, glbsrc); } else { return DelayQuad(0); } } static Loc getLoc(std::smatch match, int maxX, int maxY) { int col = std::stoi(match[2]); int row = 1; // Top std::string side = match[1].str(); if (side == "R") { row = col; col = maxX; } else if (side == "B") { row = maxY; } else if (side == "L") { row = col; col = 1; } int z = match[3].str()[0] - 'A'; return Loc(col - 1, row - 1, z); } void Arch::read_cst(std::istream &in) { // If two locations are specified separated by commas (for differential I/O buffers), // only the first location is actually recognized and used. // And pin A will be Positive and pin B will be Negative in any case. std::regex iobre = std::regex("IO_LOC +\"([^\"]+)\" +([^ ,;]+)(, *[^ ;]+)? *;.*[\\s\\S]*"); std::regex portre = std::regex("IO_PORT +\"([^\"]+)\" +([^;]+;).*[\\s\\S]*"); std::regex port_attrre = std::regex("([^ =;]+=[^ =;]+) *([^;]*;)"); std::regex iobelre = std::regex("IO([TRBL])([0-9]+)\\[?([A-Z])\\]?"); std::regex inslocre = std::regex("INS_LOC +\"([^\"]+)\" +R([0-9]+)C([0-9]+)\\[([0-9])\\]\\[([AB])\\] *;.*[\\s\\S]*"); std::regex clockre = std::regex("CLOCK_LOC +\"([^\"]+)\" +BUF([GS])(\\[([0-7])\\])?[^;]*;.*[\\s\\S]*"); std::smatch match, match_attr, match_pinloc; std::string line, pinline; enum { ioloc, ioport, insloc, clock } cst_type; settings.erase(id_cst); while (!in.eof()) { std::getline(in, line); cst_type = ioloc; if (!std::regex_match(line, match, iobre)) { if (std::regex_match(line, match, portre)) { cst_type = ioport; } else { if (std::regex_match(line, match, clockre)) { cst_type = clock; } else { if (std::regex_match(line, match, inslocre)) { cst_type = insloc; } else { if ((!line.empty()) && (line.rfind("//", 0) == std::string::npos)) { log_warning("Invalid constraint: %s\n", line.c_str()); } continue; } } } } IdString net = id(match[1]); auto it = cells.find(net); if (cst_type != clock && it == cells.end()) { log_info("Cell %s not found\n", net.c_str(this)); continue; } switch (cst_type) { case clock: { // CLOCK name BUFG|S=# std::string which_clock = match[2]; std::string lw = match[4]; int lw_idx = -1; if (lw.length() > 0) { lw_idx = atoi(lw.c_str()); log_info("lw_idx:%d\n", lw_idx); } if (which_clock.at(0) == 'S') { auto ni = nets.find(net); if (ni == nets.end()) { log_info("Net %s not found\n", net.c_str(this)); continue; } if (!allocate_longwire(ni->second.get(), lw_idx)) { log_info("Can't use the long wires. The %s network will use normal routing.\n", net.c_str(this)); } } else { log_info("BUFG isn't supported\n"); continue; } } break; case ioloc: { // IO_LOC name pin IdString pinname = id(match[2]); pinline = match[2]; const PinPOD *belname = pinLookup(package->pins.get(), package->num_pins, pinname.index); if (belname != nullptr) { std::string bel = IdString(belname->loc_id).str(this); it->second->setAttr(IdString(ID_BEL), bel); } else { if (std::regex_match(pinline, match_pinloc, iobelre)) { // may be it's IOx#[AB] style? Loc loc = getLoc(match_pinloc, getGridDimX(), getGridDimY()); BelId bel = getBelByLocation(loc); if (bel == BelId()) { log_error("Pin %s not found (TRBL style). \n", pinline.c_str()); } std::string belname = getCtx()->nameOfBel(bel); it->second->setAttr(IdString(ID_BEL), belname); } else { log_error("Pin %s not found (pin# style)\n", pinname.c_str(this)); } } } break; case insloc: { // INS_LOC int slice = std::stoi(match[4].str()) * 2; if (match[5].str() == "B") { ++slice; } std::string belname = std::string("R") + match[2].str() + "C" + match[3].str() + stringf("_SLICE%d", slice); it->second->setAttr(IdString(ID_BEL), belname); } break; default: { // IO_PORT attr=value std::string attr_val = match[2]; while (std::regex_match(attr_val, match_attr, port_attrre)) { std::string attr = "&"; attr += match_attr[1]; boost::algorithm::to_upper(attr); it->second->setAttr(id(attr), 1); attr_val = match_attr[2]; } } } } settings[id_cst] = 1; } // Add all MUXes for the cell void Arch::addMuxBels(const DatabasePOD *db, int row, int col) { IdString belname, bel_id; char buf[40]; int z; // make all wide luts with these parameters struct { char type; // MUX type 5,6,7,8 char bel_idx; // just bel name suffix char in_prefix[2]; // input from F or OF char in_idx[2]; // input from bel with idx } const mux_names[] = {{'5', '0', "", {'0', '1'}}, {'6', '0', "O", {'2', '0'}}, {'5', '1', "", {'2', '3'}}, {'7', '0', "O", {'5', '1'}}, {'5', '2', "", {'4', '5'}}, {'6', '1', "O", {'6', '4'}}, {'5', '3', "", {'6', '7'}}, {'8', '0', "O", {'3', '3'}}}; // 4 MUX2_LUT5, 2 MUX2_LUT6, 1 MUX2_LUT7, 1 MUX2_LUT8 for (int j = 0; j < 8; ++j) { z = j + BelZ::mux_0_z; int grow = row + 1; int gcol = col + 1; // no MUX2_LUT8 in the last column if (j == 7 && col == getGridDimX() - 2) { continue; } // bel snprintf(buf, 40, "R%dC%d_MUX2_LUT%c%c", grow, gcol, mux_names[j].type, mux_names[j].bel_idx); belname = id(buf); snprintf(buf, 40, "MUX2_LUT%c", mux_names[j].type); bel_id = id(buf); addBel(belname, bel_id, Loc(col, row, z), false); // dummy wires snprintf(buf, 40, "I0MUX%d", j); IdString id_wire_i0 = id(buf); IdString wire_i0_name = wireToGlobal(row, col, db, id_wire_i0); addWire(wire_i0_name, id_wire_i0, col, row); snprintf(buf, 40, "I1MUX%d", j); IdString id_wire_i1 = id(buf); IdString wire_i1_name = wireToGlobal(row, col, db, id_wire_i1); addWire(wire_i1_name, id_wire_i1, col, row); // dummy right pip DelayQuad delay = getWireTypeDelay(id_I0); snprintf(buf, 40, "%sF%c", mux_names[j].in_prefix, mux_names[j].in_idx[1]); IdString id_src_F = id(buf); IdString src_F = wireToGlobal(row, col, db, id_src_F); snprintf(buf, 40, "R%dC%d_%s_DUMMY_%s", grow, gcol, id_src_F.c_str(this), id_wire_i1.c_str(this)); addPip(id(buf), id_wire_i1, src_F, wire_i1_name, delay, Loc(col, row, 0)); // dummy left pip snprintf(buf, 40, "%sF%c", mux_names[j].in_prefix, mux_names[j].in_idx[0]); id_src_F = id(buf); // LUT8's I0 is wired to the right cell int src_col = col; if (j == 7) { ++src_col; delay = getWireTypeDelay(id_I01); } src_F = wireToGlobal(row, src_col, db, id_src_F); snprintf(buf, 40, "R%dC%d_%s_DUMMY_%s", grow, gcol, id_src_F.c_str(this), id_wire_i0.c_str(this)); addPip(id(buf), id_wire_i0, src_F, wire_i0_name, delay, Loc(col, row, 0)); // the MUX ports snprintf(buf, 40, "R%dC%d_OF%d", grow, gcol, j); addBelOutput(belname, id_OF, id(buf)); snprintf(buf, 40, "R%dC%d_SEL%d", grow, gcol, j); addBelInput(belname, id_SEL, id(buf)); snprintf(buf, 40, "R%dC%d_I0MUX%d", grow, gcol, j); addBelInput(belname, id_I0, id(buf)); snprintf(buf, 40, "R%dC%d_I1MUX%d", grow, gcol, j); addBelInput(belname, id_I1, id(buf)); } } void Arch::add_pllvr_ports(DatabasePOD const *db, BelsPOD const *bel, IdString belname, int row, int col) { IdString portname; for (int pid : {ID_CLKIN, ID_CLKFB, ID_FBDSEL0, ID_FBDSEL1, ID_FBDSEL2, ID_FBDSEL3, ID_FBDSEL4, ID_FBDSEL5, ID_IDSEL0, ID_IDSEL1, ID_IDSEL2, ID_IDSEL3, ID_IDSEL4, ID_IDSEL5, ID_ODSEL0, ID_ODSEL1, ID_ODSEL2, ID_ODSEL3, ID_ODSEL4, ID_ODSEL5, ID_VREN, ID_PSDA0, ID_PSDA1, ID_PSDA2, ID_PSDA3, ID_DUTYDA0, ID_DUTYDA1, ID_DUTYDA2, ID_DUTYDA3, ID_FDLY0, ID_FDLY1, ID_FDLY2, ID_FDLY3, ID_RESET, ID_RESET_P}) { portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, pid)->src_id); IdString wire = idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); if (wires.count(wire) == 0) { GlobalAliasPOD alias; alias.dest_col = col; alias.dest_row = row; alias.dest_id = portname.hash(); auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare); NPNR_ASSERT(alias_src != nullptr); int srcrow = alias_src->src_row; int srccol = alias_src->src_col; IdString srcid = IdString(alias_src->src_id); wire = wireToGlobal(srcrow, srccol, db, srcid); if (wires.count(wire) == 0) { addWire(wire, srcid, srccol, srcrow); } } addBelInput(belname, IdString(pid), wire); } for (int pid : {ID_LOCK, ID_CLKOUT, ID_CLKOUTP, ID_CLKOUTD, ID_CLKOUTD3}) { portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, pid)->src_id); addBelOutput(belname, IdString(pid), idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this))); } } void Arch::add_rpll_ports(DatabasePOD const *db, BelsPOD const *bel, IdString belname, int row, int col) { IdString portname; for (int pid : {ID_CLKIN, ID_CLKFB, ID_FBDSEL0, ID_FBDSEL1, ID_FBDSEL2, ID_FBDSEL3, ID_FBDSEL4, ID_FBDSEL5, ID_IDSEL0, ID_IDSEL1, ID_IDSEL2, ID_IDSEL3, ID_IDSEL4, ID_IDSEL5, ID_ODSEL0, ID_ODSEL1, ID_ODSEL2, ID_ODSEL3, ID_ODSEL4, ID_ODSEL5, ID_PSDA0, ID_PSDA1, ID_PSDA2, ID_PSDA3, ID_DUTYDA0, ID_DUTYDA1, ID_DUTYDA2, ID_DUTYDA3, ID_FDLY0, ID_FDLY1, ID_FDLY2, ID_FDLY3, ID_RESET, ID_RESET_P}) { const PairPOD *port = pairLookup(bel->ports.get(), bel->num_ports, pid); // old base if (port == nullptr) { log_warning("When building nextpnr, obsolete old apicula bases were used. Probably not working properly " "with PLL.\n"); return; } portname = IdString(port->src_id); IdString wire = idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); if (wires.count(wire) == 0) { GlobalAliasPOD alias; alias.dest_col = col; alias.dest_row = row; alias.dest_id = portname.hash(); auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare); NPNR_ASSERT(alias_src != nullptr); int srcrow = alias_src->src_row; int srccol = alias_src->src_col; IdString srcid = IdString(alias_src->src_id); wire = wireToGlobal(srcrow, srccol, db, srcid); if (wires.count(wire) == 0) { addWire(wire, srcid, srccol, srcrow); } } addBelInput(belname, IdString(pid), wire); } for (int pid : {ID_LOCK, ID_CLKOUT, ID_CLKOUTP, ID_CLKOUTD, ID_CLKOUTD3}) { portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, pid)->src_id); IdString wire = idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); if (wires.count(wire) == 0) { GlobalAliasPOD alias; alias.dest_col = col; alias.dest_row = row; alias.dest_id = portname.hash(); auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare); NPNR_ASSERT(alias_src != nullptr); int srcrow = alias_src->src_row; int srccol = alias_src->src_col; IdString srcid = IdString(alias_src->src_id); wire = wireToGlobal(srcrow, srccol, db, srcid); if (wires.count(wire) == 0) { addWire(wire, srcid, srccol, srcrow); } } addBelOutput(belname, IdString(pid), wire); } } Arch::Arch(ArchArgs args) : args(args) { family = args.family; max_clock = 6; if (family == "GW1NZ-1") { max_clock = 3; } // Load database std::string chipdb = stringf("gowin/chipdb-%s.bin", family.c_str()); auto db = reinterpret_cast(get_chipdb(chipdb)); if (db == nullptr) { log_error("Failed to load chipdb '%s'\n", chipdb.c_str()); } if (db->version != chipdb_version) { log_error("Incorrect chipdb version %u is used. Version %u is required\n", db->version, chipdb_version); } if (db->family.get() != family) { log_error("Database is for family '%s' but provided device is family '%s'.\n", db->family.get(), family.c_str()); } // setup id strings for (size_t i = 0; i < db->num_ids; i++) { IdString::initialize_add(this, db->id_strs[i].get(), uint32_t(i) + db->num_constids); } // Empty decal addDecalGraphic(IdString(), GraphicElement()); if (args.gui) { #ifndef NO_GUI // decals gfxCreateBelDecals(this); #endif } // setup package IdString package_name; IdString device_id; IdString speed_id; for (unsigned int i = 0; i < db->num_partnumbers; i++) { auto partnumber = &db->partnumber_packages[i]; // std::cout << IdString(partnumber->name_id).str(this) << IdString(partnumber->package_id).str(this) << // std::endl; if (IdString(partnumber->name_id) == id(args.partnumber)) { package_name = IdString(partnumber->package_id); device_id = IdString(partnumber->device_id); speed_id = IdString(partnumber->speed_id); break; } } if (package_name == IdString()) { log_error("Unsupported partnumber '%s'.\n", args.partnumber.c_str()); } // setup timing info speed = nullptr; for (unsigned int i = 0; i < db->num_speeds; i++) { const TimingClassPOD *tc = &db->speeds[i]; // std::cout << IdString(tc->name_id).str(this) << std::endl; if (IdString(tc->name_id) == speed_id) { speed = tc->groups.get(); break; } } if (speed == nullptr) { log_error("Unsupported speed grade '%s'.\n", speed_id.c_str(this)); } const VariantPOD *variant = nullptr; for (unsigned int i = 0; i < db->num_variants; i++) { auto var = &db->variants[i]; // std::cout << IdString(var->name_id).str(this) << std::endl; if (IdString(var->name_id) == device_id) { variant = var; break; } } if (variant == nullptr) { log_error("Unsupported device grade '%s'.\n", device_id.c_str(this)); } package = nullptr; for (unsigned int i = 0; i < variant->num_packages; i++) { auto pkg = &variant->packages[i]; // std::cout << IdString(pkg->name_id).str(this) << std::endl; if (IdString(pkg->name_id) == package_name) { package = pkg; break; } // for (int j=0; j < pkg->num_pins; j++) { // auto pin = pkg->pins[j]; // std::cout << IdString(pin.src_id).str(this) << " " << IdString(pin.dest_id).str(this) << std::endl; // } } if (package == nullptr) { log_error("Unsupported package '%s'.\n", package_name.c_str(this)); } // log_info("Series:%s Device:%s Package:%s Speed:%s\n", family.c_str(), device_id.c_str(this), package_name.c_str(this), speed_id.c_str(this)); device = device_id.str(this); // setup db // add global VCC and GND bels addBel(id_GND, id_GND, Loc(0, 0, BelZ::gnd_0_z), true); addWire(id_VSS, id_VSS, 0, 0); addBelOutput(id_GND, id_G, id_VSS); addBel(id_VCC, id_VCC, Loc(0, 0, BelZ::vcc_0_z), true); addWire(id_VCC, id_VCC, 0, 0); addBelOutput(id_VCC, id_V, id_VCC); char buf[32]; // The reverse order of the enumeration simplifies the creation // of MUX2_LUT8s: they need the existence of the wire on the right. for (int i = db->rows * db->cols - 1; i >= 0; --i) { IdString grpname; int row = i / db->cols; int col = i % db->cols; const TilePOD *tile = db->grid[i].get(); if (args.gui) { #ifndef NO_GUI // CRU decal snprintf(buf, 32, "R%dC%d_CRU", row + 1, col + 1); grpname = id(buf); addGroup(grpname); setGroupDecal(grpname, gfxGetCruGroupDecalXY(col, row)); #endif } // setup wires const PairPOD *pips[2] = {tile->pips.get(), tile->clock_pips.get()}; unsigned int num_pips[2] = {tile->num_pips, tile->num_clock_pips}; for (int p = 0; p < 2; p++) { for (unsigned int j = 0; j < num_pips[p]; j++) { const PairPOD pip = pips[p][j]; int destrow = row; int destcol = col; IdString destid(pip.dest_id), gdestid(pip.dest_id); IdString gdestname = wireToGlobal(destrow, destcol, db, gdestid); if (wires.count(gdestname) == 0) addWire(gdestname, destid, destcol, destrow); int srcrow = row; int srccol = col; IdString srcid(pip.src_id), gsrcid(pip.src_id); IdString gsrcname = wireToGlobal(srcrow, srccol, db, gsrcid); if (wires.count(gsrcname) == 0) addWire(gsrcname, srcid, srccol, srcrow); } } for (unsigned int j = 0; j < tile->num_bels; j++) { const BelsPOD *bel = &tile->bels[j]; IdString belname; IdString portname; int z = 0; bool dff = true; bool oddrc = false; switch (static_cast(bel->type_id)) { case ID_PLLVR: belname = idf("R%dC%d_PLLVR", row + 1, col + 1); addBel(belname, id_PLLVR, Loc(col, row, BelZ::pllvr_z), false); add_pllvr_ports(db, bel, belname, row, col); break; case ID_RPLLA: belname = idf("R%dC%d_rPLL", row + 1, col + 1); addBel(belname, id_rPLL, Loc(col, row, BelZ::pll_z), false); add_rpll_ports(db, bel, belname, row, col); break; case ID_BUFS7: z++; /* fall-through*/ case ID_BUFS6: z++; /* fall-through*/ case ID_BUFS5: z++; /* fall-through*/ case ID_BUFS4: z++; /* fall-through*/ case ID_BUFS3: z++; /* fall-through*/ case ID_BUFS2: z++; /* fall-through*/ case ID_BUFS1: z++; /* fall-through*/ case ID_BUFS0: snprintf(buf, 32, "R%dC%d_BUFS%d", row + 1, col + 1, z); belname = id(buf); addBel(belname, id_BUFS, Loc(col, row, BelZ::bufs_0_z + z), false); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_I)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_I, id(buf)); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_O)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelOutput(belname, id_O, id(buf)); break; case ID_GSR0: snprintf(buf, 32, "R%dC%d_GSR0", row + 1, col + 1); belname = id(buf); addBel(belname, id_GSR, Loc(col, row, 0), false); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_GSRI)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_GSRI, id(buf)); break; case ID_OSC: snprintf(buf, 32, "R%dC%d_OSC", row + 1, col + 1); belname = id(buf); addBel(belname, id_OSC, Loc(col, row, BelZ::osc_z), false); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OSCOUT)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelOutput(belname, id_OSCOUT, id(buf)); break; case ID_OSCH: snprintf(buf, 32, "R%dC%d_OSCH", row + 1, col + 1); belname = id(buf); addBel(belname, id_OSCH, Loc(col, row, BelZ::osc_z), false); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OSCOUT)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelOutput(belname, id_OSCOUT, id(buf)); break; case ID_OSCF: snprintf(buf, 32, "R%dC%d_OSCF", row + 1, col + 1); belname = id(buf); addBel(belname, id_OSCF, Loc(col, row, BelZ::osc_z), false); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OSCOUT)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelOutput(belname, id_OSCOUT, id(buf)); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OSCEN)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_OSCEN, id(buf)); break; case ID_OSCZ: snprintf(buf, 32, "R%dC%d_OSCZ", row + 1, col + 1); belname = id(buf); addBel(belname, id_OSCZ, Loc(col, row, BelZ::osc_z), false); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OSCOUT)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelOutput(belname, id_OSCOUT, id(buf)); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OSCEN)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_OSCEN, id(buf)); break; case ID_RAM16: snprintf(buf, 32, "R%dC%d_RAMW", row + 1, col + 1); belname = id(buf); addBel(belname, id_RAMW, Loc(col, row, BelZ::lutram_0_z), false); snprintf(buf, 32, "R%dC%d_A%d", row + 1, col + 1, 4); addBelInput(belname, id_A4, id(buf)); snprintf(buf, 32, "R%dC%d_B%d", row + 1, col + 1, 4); addBelInput(belname, id_B4, id(buf)); snprintf(buf, 32, "R%dC%d_C%d", row + 1, col + 1, 4); addBelInput(belname, id_C4, id(buf)); snprintf(buf, 32, "R%dC%d_D%d", row + 1, col + 1, 4); addBelInput(belname, id_D4, id(buf)); snprintf(buf, 32, "R%dC%d_A%d", row + 1, col + 1, 5); addBelInput(belname, id_A5, id(buf)); snprintf(buf, 32, "R%dC%d_B%d", row + 1, col + 1, 5); addBelInput(belname, id_B5, id(buf)); snprintf(buf, 32, "R%dC%d_C%d", row + 1, col + 1, 5); addBelInput(belname, id_C5, id(buf)); snprintf(buf, 32, "R%dC%d_D%d", row + 1, col + 1, 5); addBelInput(belname, id_D5, id(buf)); snprintf(buf, 32, "R%dC%d_CLK%d", row + 1, col + 1, 2); addBelInput(belname, id_CLK, id(buf)); snprintf(buf, 32, "R%dC%d_LSR%d", row + 1, col + 1, 2); addBelInput(belname, id_LSR, id(buf)); snprintf(buf, 32, "R%dC%d_CE%d", row + 1, col + 1, 2); addBelInput(belname, id_CE, id(buf)); break; // fall through the ++ case ID_LUT7: z++; dff = false; /* fall-through*/ case ID_LUT6: z++; dff = false; /* fall-through*/ case ID_LUT5: z++; /* fall-through*/ case ID_LUT4: z++; /* fall-through*/ case ID_LUT3: z++; /* fall-through*/ case ID_LUT2: z++; /* fall-through*/ case ID_LUT1: z++; /* fall-through*/ case ID_LUT0: // common LUT+DFF code snprintf(buf, 32, "R%dC%d_SLICE%d", row + 1, col + 1, z); belname = id(buf); addBel(belname, id_SLICE, Loc(col, row, z), false); snprintf(buf, 32, "R%dC%d_F%d", row + 1, col + 1, z); addBelOutput(belname, id_F, id(buf)); snprintf(buf, 32, "R%dC%d_A%d", row + 1, col + 1, z); addBelInput(belname, id_A, id(buf)); snprintf(buf, 32, "R%dC%d_B%d", row + 1, col + 1, z); addBelInput(belname, id_B, id(buf)); snprintf(buf, 32, "R%dC%d_C%d", row + 1, col + 1, z); addBelInput(belname, id_C, id(buf)); snprintf(buf, 32, "R%dC%d_D%d", row + 1, col + 1, z); addBelInput(belname, id_D, id(buf)); if (dff) { snprintf(buf, 32, "R%dC%d_CLK%d", row + 1, col + 1, z / 2); addBelInput(belname, id_CLK, id(buf)); snprintf(buf, 32, "R%dC%d_LSR%d", row + 1, col + 1, z / 2); addBelInput(belname, id_LSR, id(buf)); snprintf(buf, 32, "R%dC%d_CE%d", row + 1, col + 1, z / 2); addBelInput(belname, id_CE, id(buf)); snprintf(buf, 32, "R%dC%d_Q%d", row + 1, col + 1, z); addBelOutput(belname, id_Q, id(buf)); } if (z == 0) { addMuxBels(db, row, col); } if (z % 2 == 0) { snprintf(buf, 32, "R%dC%d_LUT_GRP%d", row + 1, col + 1, z); grpname = id(buf); if (args.gui) { #ifndef NO_GUI addGroup(grpname); setGroupDecal(grpname, gfxGetLutGroupDecalXY(col, row, z >> 1)); #endif } } break; case ID_IOBJ: z++; /* fall-through*/ case ID_IOBI: z++; /* fall-through*/ case ID_IOBH: z++; /* fall-through*/ case ID_IOBG: z++; /* fall-through*/ case ID_IOBF: z++; /* fall-through*/ case ID_IOBE: z++; /* fall-through*/ case ID_IOBD: z++; /* fall-through*/ case ID_IOBC: z++; /* fall-through*/ case ID_IOBB: z++; /* fall-through*/ case ID_IOBA: { snprintf(buf, 32, "R%dC%d_IOB%c", row + 1, col + 1, 'A' + z); belname = id(buf); addBel(belname, id_IOB, Loc(col, row, z), false); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_O)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelOutput(belname, id_O, id(buf)); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_I)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_I, id(buf)); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OE)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_OEN, id(buf)); // GW1NR-9 quirk const PairPOD *xxx_port = pairLookup(bel->ports.get(), bel->num_ports, ID_XXX_VSS0); if (xxx_port != nullptr) { gw1n9_quirk = true; portname = IdString(xxx_port->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_XXX_VSS0, id(buf)); } xxx_port = pairLookup(bel->ports.get(), bel->num_ports, ID_XXX_VSS1); if (xxx_port != nullptr) { gw1n9_quirk = true; portname = IdString(xxx_port->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_XXX_VSS1, id(buf)); } } break; // Simplified IO case ID_IOBJS: z++; /* fall-through*/ case ID_IOBIS: z++; /* fall-through*/ case ID_IOBHS: z++; /* fall-through*/ case ID_IOBGS: z++; /* fall-through*/ case ID_IOBFS: z++; /* fall-through*/ case ID_IOBES: z++; /* fall-through*/ case ID_IOBDS: z++; /* fall-through*/ case ID_IOBCS: z++; /* fall-through*/ case ID_IOBBS: z++; /* fall-through*/ case ID_IOBAS: snprintf(buf, 32, "R%dC%d_IOB%c", row + 1, col + 1, 'A' + z); belname = id(buf); addBel(belname, id_IOBS, Loc(col, row, z), false); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_O)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelOutput(belname, id_O, id(buf)); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_I)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_I, id(buf)); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OE)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_OEN, id(buf)); break; // IO logic case ID_ODDRCB: z++; /* fall-through*/ case ID_ODDRCA: oddrc = true; z++; /* fall-through*/ case ID_ODDRB: z++; /* fall-through*/ case ID_ODDRA: { snprintf(buf, 32, "R%dC%d_ODDR%s%c", row + 1, col + 1, oddrc ? "C" : "", 'A' + z - (oddrc ? 2 : 0)); belname = id(buf); addBel(belname, id_ODDR, Loc(col, row, BelZ::iologic_0_z + z), false); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_D0)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_D0, id(buf)); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_D1)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_D1, id(buf)); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_TX)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_TX, id(buf)); portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_CLK)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_CLK, id(buf)); const PairPOD *xxx_port = pairLookup(bel->ports.get(), bel->num_ports, ID_XXX_VSS); if (xxx_port != nullptr) { ddr_has_extra_inputs = true; portname = IdString(xxx_port->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_XXX_VSS, id(buf)); } xxx_port = pairLookup(bel->ports.get(), bel->num_ports, ID_XXX_VCC); if (xxx_port != nullptr) { ddr_has_extra_inputs = true; portname = IdString(xxx_port->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_XXX_VCC, id(buf)); } if (oddrc) { portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_CE)->src_id); snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this)); addBelInput(belname, id_CE, id(buf)); } // dummy wires snprintf(buf, 32, "ODDR%s%c_Q0", oddrc ? "C" : "", 'A' + z - (oddrc ? 2 : 0)); IdString id_q0 = id(buf); IdString q0_name = wireToGlobal(row, col, db, id_q0); if (wires.count(q0_name) == 0) addWire(q0_name, id_q0, row, col); addBelOutput(belname, id_Q0, q0_name); snprintf(buf, 32, "ODDR%s%c_Q1", oddrc ? "C" : "", 'A' + z - (oddrc ? 2 : 0)); IdString id_q1 = id(buf); IdString q1_name = wireToGlobal(row, col, db, id_q1); if (wires.count(q1_name) == 0) addWire(q1_name, id_q1, row, col); addBelOutput(belname, id_Q1, q1_name); } break; default: break; } } } // IO pin configs for (unsigned int i = 0; i < package->num_pins; i++) { const PinPOD *pin = &package->pins[i]; if (pin->num_cfgs == 0) { continue; } auto b = bels.find(IdString(pin->loc_id)); if (b == bels.end()) { // Not all pins are transmitted, e.g. MODE, DONE etc. continue; } std::vector &cfgs = b->second.pin_cfgs; for (unsigned int j = 0; j < pin->num_cfgs; ++j) { cfgs.push_back(IdString(pin->cfgs[j])); } } // setup pips for (int i = 0; i < db->rows * db->cols; i++) { int row = i / db->cols; int col = i % db->cols; const TilePOD *tile = db->grid[i].get(); const PairPOD *pips[2] = {tile->pips.get(), tile->clock_pips.get()}; unsigned int num_pips[2] = {tile->num_pips, tile->num_clock_pips}; for (int p = 0; p < 2; p++) { for (unsigned int j = 0; j < num_pips[p]; j++) { const PairPOD pip = pips[p][j]; int destrow = row; int destcol = col; IdString destid(pip.dest_id), gdestid(pip.dest_id); IdString gdestname = wireToGlobal(destrow, destcol, db, gdestid); int srcrow = row; int srccol = col; IdString srcid(pip.src_id), gsrcid(pip.src_id); IdString gsrcname = wireToGlobal(srcrow, srccol, db, gsrcid); snprintf(buf, 32, "R%dC%d_%s_%s", row + 1, col + 1, srcid.c_str(this), destid.c_str(this)); IdString pipname = id(buf); DelayQuad delay = getWireTypeDelay(destid); // global alias srcid = IdString(pip.src_id); GlobalAliasPOD alias; alias.dest_col = srccol; alias.dest_row = srcrow; alias.dest_id = srcid.index; auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare); if (alias_src != nullptr) { srccol = alias_src->src_col; srcrow = alias_src->src_row; srcid = IdString(alias_src->src_id); gsrcname = wireToGlobal(srcrow, srccol, db, srcid); } addPip(pipname, destid, gsrcname, gdestname, delay, Loc(col, row, j)); } } } if (args.gui) { setDefaultDecals(); } // Permissible combinations of modes in a single slice dff_comp_mode[id_DFF] = id_DFF; dff_comp_mode[id_DFFE] = id_DFFE; dff_comp_mode[id_DFFS] = id_DFFR; dff_comp_mode[id_DFFR] = id_DFFS; dff_comp_mode[id_DFFSE] = id_DFFRE; dff_comp_mode[id_DFFRE] = id_DFFSE; dff_comp_mode[id_DFFP] = id_DFFC; dff_comp_mode[id_DFFC] = id_DFFP; dff_comp_mode[id_DFFPE] = id_DFFCE; dff_comp_mode[id_DFFCE] = id_DFFPE; dff_comp_mode[id_DFFNS] = id_DFFNR; dff_comp_mode[id_DFFNR] = id_DFFNS; dff_comp_mode[id_DFFNSE] = id_DFFNRE; dff_comp_mode[id_DFFNRE] = id_DFFNSE; dff_comp_mode[id_DFFNP] = id_DFFNC; dff_comp_mode[id_DFFNC] = id_DFFNP; dff_comp_mode[id_DFFNPE] = id_DFFNCE; dff_comp_mode[id_DFFNCE] = id_DFFNPE; BaseArch::init_cell_types(); BaseArch::init_bel_buckets(); } void IdString::initialize_arch(const BaseCtx *ctx) { #define X(t) initialize_add(ctx, #t, ID_##t); #include "constids.inc" #undef X } // --------------------------------------------------------------- BelId Arch::getBelByName(IdStringList name) const { if (bels.count(name[0])) return name[0]; return BelId(); } IdStringList Arch::getBelName(BelId bel) const { return IdStringList(bel); } Loc Arch::getBelLocation(BelId bel) const { auto &info = bels.at(bel); return Loc(info.x, info.y, info.z); } BelId Arch::getBelByLocation(Loc loc) const { auto it = bel_by_loc.find(loc); if (it != bel_by_loc.end()) return it->second; return BelId(); } const std::vector &Arch::getBelsByTile(int x, int y) const { return bels_by_tile.at(x).at(y); } bool Arch::haveBelType(int x, int y, IdString bel_type) { for (auto bel : getBelsByTile(x, y)) { BelInfo bi = bel_info(bel); if (bi.type == bel_type) { return true; } } return false; } bool Arch::getBelGlobalBuf(BelId bel) const { return bels.at(bel).gb; } void Arch::bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) { bels.at(bel).bound_cell = cell; cell->bel = bel; cell->belStrength = strength; refreshUiBel(bel); } void Arch::unbindBel(BelId bel) { bels.at(bel).bound_cell->bel = BelId(); bels.at(bel).bound_cell->belStrength = STRENGTH_NONE; bels.at(bel).bound_cell = nullptr; refreshUiBel(bel); } bool Arch::checkBelAvail(BelId bel) const { return bels.at(bel).bound_cell == nullptr; } CellInfo *Arch::getBoundBelCell(BelId bel) const { return bels.at(bel).bound_cell; } CellInfo *Arch::getConflictingBelCell(BelId bel) const { return bels.at(bel).bound_cell; } const std::vector &Arch::getBels() const { return bel_ids; } IdString Arch::getBelType(BelId bel) const { return bels.at(bel).type; } const std::map &Arch::getBelAttrs(BelId bel) const { return bels.at(bel).attrs; } WireId Arch::getBelPinWire(BelId bel, IdString pin) const { const auto &bdata = bels.at(bel); if (!bdata.pins.count(pin)) log_error("bel '%s' has no pin '%s'\n", bel.c_str(this), pin.c_str(this)); return bdata.pins.at(pin).wire; } PortType Arch::getBelPinType(BelId bel, IdString pin) const { return bels.at(bel).pins.at(pin).type; } std::vector Arch::getBelPins(BelId bel) const { std::vector ret; for (auto &it : bels.at(bel).pins) ret.push_back(it.first); return ret; } std::array Arch::getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const { return {pin}; } // --------------------------------------------------------------- WireId Arch::getWireByName(IdStringList name) const { if (wires.count(name[0])) return name[0]; return WireId(); } IdStringList Arch::getWireName(WireId wire) const { return IdStringList(wire); } IdString Arch::getWireType(WireId wire) const { return wires.at(wire).type; } const std::map &Arch::getWireAttrs(WireId wire) const { return wires.at(wire).attrs; } void Arch::bindWire(WireId wire, NetInfo *net, PlaceStrength strength) { wires.at(wire).bound_net = net; net->wires[wire].pip = PipId(); net->wires[wire].strength = strength; refreshUiWire(wire); } void Arch::unbindWire(WireId wire) { auto &net_wires = wires.at(wire).bound_net->wires; auto pip = net_wires.at(wire).pip; if (pip != PipId()) { pips.at(pip).bound_net = nullptr; refreshUiPip(pip); } net_wires.erase(wire); wires.at(wire).bound_net = nullptr; refreshUiWire(wire); } bool Arch::checkWireAvail(WireId wire) const { return wires.at(wire).bound_net == nullptr; } NetInfo *Arch::getBoundWireNet(WireId wire) const { return wires.at(wire).bound_net; } NetInfo *Arch::getConflictingWireNet(WireId wire) const { return wires.at(wire).bound_net; } const std::vector &Arch::getWireBelPins(WireId wire) const { return wires.at(wire).bel_pins; } const std::vector &Arch::getWires() const { return wire_ids; } // --------------------------------------------------------------- PipId Arch::getPipByName(IdStringList name) const { if (pips.count(name[0])) return name[0]; return PipId(); } IdStringList Arch::getPipName(PipId pip) const { return IdStringList(pip); } IdString Arch::getPipType(PipId pip) const { return pips.at(pip).type; } const std::map &Arch::getPipAttrs(PipId pip) const { return pips.at(pip).attrs; } void Arch::bindPip(PipId pip, NetInfo *net, PlaceStrength strength) { WireId wire = pips.at(pip).dstWire; pips.at(pip).bound_net = net; wires.at(wire).bound_net = net; net->wires[wire].pip = pip; net->wires[wire].strength = strength; refreshUiPip(pip); refreshUiWire(wire); } void Arch::unbindPip(PipId pip) { WireId wire = pips.at(pip).dstWire; wires.at(wire).bound_net->wires.erase(wire); pips.at(pip).bound_net = nullptr; wires.at(wire).bound_net = nullptr; refreshUiPip(pip); refreshUiWire(wire); } bool Arch::checkPipAvail(PipId pip) const { return pips.at(pip).bound_net == nullptr; } NetInfo *Arch::getBoundPipNet(PipId pip) const { return pips.at(pip).bound_net; } NetInfo *Arch::getConflictingPipNet(PipId pip) const { return pips.at(pip).bound_net; } WireId Arch::getConflictingPipWire(PipId pip) const { return pips.at(pip).bound_net ? pips.at(pip).dstWire : WireId(); } const std::vector &Arch::getPips() const { return pip_ids; } Loc Arch::getPipLocation(PipId pip) const { return pips.at(pip).loc; } WireId Arch::getPipSrcWire(PipId pip) const { return pips.at(pip).srcWire; } WireId Arch::getPipDstWire(PipId pip) const { return pips.at(pip).dstWire; } DelayQuad Arch::getPipDelay(PipId pip) const { return pips.at(pip).delay; } const std::vector &Arch::getPipsDownhill(WireId wire) const { return wires.at(wire).downhill; } const std::vector &Arch::getPipsUphill(WireId wire) const { return wires.at(wire).uphill; } // --------------------------------------------------------------- GroupId Arch::getGroupByName(IdStringList name) const { return name[0]; } IdStringList Arch::getGroupName(GroupId group) const { return IdStringList(group); } std::vector Arch::getGroups() const { std::vector ret; for (auto &it : groups) ret.push_back(it.first); return ret; } const std::vector &Arch::getGroupBels(GroupId group) const { return groups.at(group).bels; } const std::vector &Arch::getGroupWires(GroupId group) const { return groups.at(group).wires; } const std::vector &Arch::getGroupPips(GroupId group) const { return groups.at(group).pips; } const std::vector &Arch::getGroupGroups(GroupId group) const { return groups.at(group).groups; } // --------------------------------------------------------------- delay_t Arch::estimateDelay(WireId src, WireId dst) const { const WireInfo &s = wires.at(src); const WireInfo &d = wires.at(dst); int dx = abs(s.x - d.x); int dy = abs(s.y - d.y); return (dx + dy) * args.delayScale + args.delayOffset; } delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const { NPNR_UNUSED(src_pin); NPNR_UNUSED(dst_pin); auto driver_loc = getBelLocation(src_bel); auto sink_loc = getBelLocation(dst_bel); int dx = abs(sink_loc.x - driver_loc.x); int dy = abs(sink_loc.y - driver_loc.y); return (dx + dy) * args.delayScale + args.delayOffset; } bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; } BoundingBox Arch::getRouteBoundingBox(WireId src, WireId dst) const { BoundingBox bb; int src_x = wires.at(src).x; int src_y = wires.at(src).y; int dst_x = wires.at(dst).x; int dst_y = wires.at(dst).y; bb.x0 = src_x; bb.y0 = src_y; bb.x1 = src_x; bb.y1 = src_y; auto extend = [&](int x, int y) { bb.x0 = std::min(bb.x0, x); bb.x1 = std::max(bb.x1, x); bb.y0 = std::min(bb.y0, y); bb.y1 = std::max(bb.y1, y); }; extend(dst_x, dst_y); return bb; } // --------------------------------------------------------------- bool Arch::place() { std::string placer = str_or_default(settings, id_placer, defaultPlacer); bool retVal; if (placer == "heap") { bool have_iobuf_or_constr = false; for (auto &cell : cells) { CellInfo *ci = cell.second.get(); if (ci->type == id_IOB || ci->bel != BelId() || ci->attrs.count(id_BEL)) { have_iobuf_or_constr = true; break; } } if (!have_iobuf_or_constr) { log_warning("Unable to use HeAP due to a lack of IO buffers or constrained cells as anchors; reverting to " "SA.\n"); retVal = placer1(getCtx(), Placer1Cfg(getCtx())); } else { PlacerHeapCfg cfg(getCtx()); cfg.ioBufTypes.insert(id_IOB); cfg.beta = 0.5; retVal = placer_heap(getCtx(), cfg); } getCtx()->settings[id_place] = 1; archInfoToAttributes(); } else if (placer == "sa") { retVal = placer1(getCtx(), Placer1Cfg(getCtx())); getCtx()->settings[id_place] = 1; archInfoToAttributes(); return retVal; } else { log_error("Gowin architecture does not support placer '%s'\n", placer.c_str()); } // debug placement if (getCtx()->debug) { for (auto &cell : getCtx()->cells) { log_info("Placed: %s -> %s\n", cell.first.c_str(getCtx()), getCtx()->nameOfBel(cell.second->bel)); } } return retVal; } static bool is_spec_iob(const Context *ctx, const CellInfo *cell, IdString pin_name) { if (!is_iob(ctx, cell)) { return false; } std::vector const &cfgs = ctx->bels.at(cell->bel).pin_cfgs; bool have_pin = std::find(cfgs.begin(), cfgs.end(), pin_name) != cfgs.end(); return have_pin; } static bool is_RPLL_T_IN_iob(const Context *ctx, const CellInfo *cell) { return is_spec_iob(ctx, cell, ctx->id("RPLL_T_IN")); } static bool is_LPLL_T_IN_iob(const Context *ctx, const CellInfo *cell) { return is_spec_iob(ctx, cell, ctx->id("LPLL_T_IN")); } static bool is_RPLL_T_FB_iob(const Context *ctx, const CellInfo *cell) { return is_spec_iob(ctx, cell, ctx->id("RPLL_T_FB")); } static bool is_LPLL_T_FB_iob(const Context *ctx, const CellInfo *cell) { return is_spec_iob(ctx, cell, ctx->id("LPLL_T_FB")); } bool Arch::is_GCLKT_iob(const CellInfo *cell) { for (int i = 0; i < 6; ++i) { if (is_spec_iob(getCtx(), cell, idf("GCLKT_%d", i))) { return true; } } return false; } void Arch::bind_pll_to_bel(CellInfo *ci, PLL loc) { BelId bel; switch (ci->type.hash()) { case ID_PLLVR: bel = loc == PLL::left ? id("R1C28_PLLVR") : id("R1C37_PLLVR"); break; case ID_rPLL: if (family == "GW1N-1" || family == "GW1NZ-1") { if (loc == PLL::left) { return; } bel = id("R1C18_rPLL"); break; } if (family == "GW1NR-9C" || family == "GW1NR-9") { bel = loc == PLL::left ? id("R10C1_rPLL") : id("R10C47_rPLL"); break; } break; default: return; } if (checkBelAvail(bel) || ci->belStrength != STRENGTH_LOCKED) { if (ci->bel == bel) { unbindBel(bel); } else { if (!checkBelAvail(bel) && ci->belStrength != STRENGTH_LOCKED) { CellInfo *other_ci = getBoundBelCell(bel); unbindBel(bel); BelId our_bel = ci->bel; unbindBel(our_bel); bindBel(our_bel, other_ci, STRENGTH_LOCKED); } } ci->disconnectPort(id_CLKIN); ci->setParam(id_INSEL, Property("CLKIN0")); bindBel(bel, ci, STRENGTH_LOCKED); } } // If the PLL input can be connected using a direct wire, then do so, // bypassing conventional routing. void Arch::fix_pll_nets(Context *ctx) { for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type != id_rPLL && ci->type != id_PLLVR) { continue; } // *** CLKIN do { if (!port_used(ci, id_CLKIN)) { ci->setParam(id_INSEL, Property("UNKNOWN")); break; } NetInfo *net = ci->getPort(id_CLKIN); if (net->name == id("$PACKER_VCC_NET") || net->name == id("$PACKER_GND_NET")) { ci->setParam(id_INSEL, Property("UNKNOWN")); break; } if (net_driven_by(ctx, net, is_RPLL_T_IN_iob, id_O) != nullptr) { bind_pll_to_bel(ci, PLL::right); break; } if (net_driven_by(ctx, net, is_LPLL_T_IN_iob, id_O) != nullptr) { bind_pll_to_bel(ci, PLL::left); break; } // XXX do special bels (HCLK etc) // This is general routing through CLK0 pip ci->setParam(id_INSEL, Property("CLKIN1")); } while (0); do { // *** CLKFB if (str_or_default(ci->params, id_CLKFB_SEL, "internal") == "internal") { ci->setParam(id_FBSEL, Property("CLKFB3")); continue; } if (!port_used(ci, id_CLKFB)) { ci->setParam(id_FBSEL, Property("UNKNOWN")); continue; } NetInfo *net = ci->getPort(id_CLKFB); if (net->name == id("$PACKER_VCC_NET") || net->name == id("$PACKER_GND_NET")) { ci->setParam(id_FBSEL, Property("UNKNOWN")); continue; } // XXX Redesign for chips other than N-1 and NS-4 if (net_driven_by(ctx, net, is_RPLL_T_FB_iob, id_O) != nullptr) { ci->disconnectPort(id_CLKFB); ci->setParam(id_FBSEL, Property("CLKFB2")); break; } if (net_driven_by(ctx, net, is_LPLL_T_FB_iob, id_O) != nullptr) { ci->disconnectPort(id_CLKFB); ci->setParam(id_FBSEL, Property("CLKFB2")); break; } // XXX do special bels (HCLK etc) // This is general routing through CLK2 pip ci->setParam(id_FBSEL, Property("CLKFB0")); } while (0); } } void Arch::pre_route(Context *ctx) { fix_pll_nets(ctx); if (bool_or_default(settings, id("arch.enable-globals"))) { mark_gowin_globals(ctx); } } void Arch::post_route(Context *ctx) { fix_longwire_bels(); } bool Arch::route() { std::string router = str_or_default(settings, id_router, defaultRouter); Context *ctx = getCtx(); pre_route(ctx); if (bool_or_default(settings, id("arch.enable-globals"))) { route_gowin_globals(ctx); } bool result; if (router == "router1") { result = router1(ctx, Router1Cfg(ctx)); } else if (router == "router2") { router2(ctx, Router2Cfg(ctx)); result = true; } else { log_error("Gowin architecture does not support router '%s'\n", router.c_str()); } getCtx()->settings[id_route] = 1; archInfoToAttributes(); post_route(ctx); return result; } // --------------------------------------------------------------- std::vector Arch::getDecalGraphics(DecalId decal) const { if (!decal_graphics.count(decal)) { // XXX return std::vector(); } return decal_graphics.at(decal); } // --------------------------------------------------------------- bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const { if (!cellTiming.count(cell->name)) return false; const auto &tmg = cellTiming.at(cell->name); auto fnd = tmg.combDelays.find(CellDelayKey{fromPort, toPort}); if (fnd != tmg.combDelays.end()) { delay = fnd->second; return true; } else { return false; } } // Get the port class, also setting clockPort if applicable TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const { if (!cellTiming.count(cell->name)) return TMG_IGNORE; const auto &tmg = cellTiming.at(cell->name); if (tmg.clockingInfo.count(port)) clockInfoCount = int(tmg.clockingInfo.at(port).size()); else clockInfoCount = 0; return get_or_default(tmg.portClasses, port, TMG_IGNORE); } TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const { NPNR_ASSERT(cellTiming.count(cell->name)); const auto &tmg = cellTiming.at(cell->name); NPNR_ASSERT(tmg.clockingInfo.count(port)); return tmg.clockingInfo.at(port).at(index); } bool Arch::isBelLocationValid(BelId bel, bool explain_invalid) const { Loc loc = getBelLocation(bel); std::vector cells; for (auto tbel : getBelsByTile(loc.x, loc.y)) { CellInfo *bound = getBoundBelCell(tbel); if (bound != nullptr) cells.push_back(bound); } return cellsCompatible(cells.data(), int(cells.size())); } #ifdef WITH_HEAP const std::string Arch::defaultPlacer = "heap"; #else const std::string Arch::defaultPlacer = "sa"; #endif const std::vector Arch::availablePlacers = {"sa", #ifdef WITH_HEAP "heap" #endif }; const std::string Arch::defaultRouter = "router1"; const std::vector Arch::availableRouters = {"router1", "router2"}; void Arch::assignArchInfo() { for (auto &cell : getCtx()->cells) { IdString cname = cell.first; CellInfo *ci = cell.second.get(); DelayQuad delay = DelayQuad(0); ci->is_slice = false; switch (ci->type.index) { case ID_SLICE: { ci->is_slice = true; ci->ff_used = ci->params.at(id_FF_USED).as_bool(); ci->ff_type = id(ci->params.at(id_FF_TYPE).as_string()); ci->slice_clk = ci->getPort(id_CLK); ci->slice_ce = ci->getPort(id_CE); ci->slice_lsr = ci->getPort(id_LSR); // add timing paths addCellTimingClock(cname, id_CLK); addCellTimingClass(cname, id_CE, TMG_REGISTER_INPUT); addCellTimingClass(cname, id_LSR, TMG_REGISTER_INPUT); IdString ports[4] = {id_A, id_B, id_C, id_D}; for (int i = 0; i < 4; i++) { DelayPair setup = delayLookup(speed->dff.timings.get(), speed->dff.num_timings, id_clksetpos).delayPair(); DelayPair hold = delayLookup(speed->dff.timings.get(), speed->dff.num_timings, id_clkholdpos).delayPair(); addCellTimingSetupHold(cname, ports[i], id_CLK, setup, hold); } DelayQuad clkout = delayLookup(speed->dff.timings.get(), speed->dff.num_timings, id_clk_qpos); addCellTimingClockToOut(cname, id_Q, id_CLK, clkout); IdString port_delay[4] = {id_a_f, id_b_f, id_c_f, id_d_f}; for (int i = 0; i < 4; i++) { DelayQuad delay = delayLookup(speed->lut.timings.get(), speed->lut.num_timings, port_delay[i]); addCellTimingDelay(cname, ports[i], id_F, delay); } break; } case ID_MUX2_LUT8: delay = delay + delayLookup(speed->lut.timings.get(), speed->lut.num_timings, id_fx_ofx1); /* FALLTHRU */ case ID_MUX2_LUT7: delay = delay + delayLookup(speed->lut.timings.get(), speed->lut.num_timings, id_fx_ofx1); /* FALLTHRU */ case ID_MUX2_LUT6: delay = delay + delayLookup(speed->lut.timings.get(), speed->lut.num_timings, id_fx_ofx1); /* FALLTHRU */ case ID_MUX2_LUT5: { delay = delay + delayLookup(speed->lut.timings.get(), speed->lut.num_timings, id_fx_ofx1); addCellTimingDelay(cname, id_I0, id_OF, delay); addCellTimingDelay(cname, id_I1, id_OF, delay); addCellTimingClass(cname, id_SEL, TMG_COMB_INPUT); break; } case ID_IOB: /* FALLTHRU */ case ID_IOBS: addCellTimingClass(cname, id_I, TMG_ENDPOINT); addCellTimingClass(cname, id_O, TMG_STARTPOINT); break; case ID_BUFS: addCellTimingClass(cname, id_I, TMG_ENDPOINT); addCellTimingClass(cname, id_O, TMG_STARTPOINT); break; default: break; } } } bool Arch::cellsCompatible(const CellInfo **cells, int count) const { const NetInfo *clk[4] = {nullptr, nullptr, nullptr, nullptr}; const NetInfo *ce[4] = {nullptr, nullptr, nullptr, nullptr}; const NetInfo *lsr[4] = {nullptr, nullptr, nullptr, nullptr}; IdString mode[4] = {IdString(), IdString(), IdString(), IdString()}; for (int i = 0; i < count; i++) { const CellInfo *ci = cells[i]; if (ci->is_slice) { Loc loc = getBelLocation(ci->bel); int cls = loc.z / 2; if (loc.z >= 6 && ci->ff_used) // top slice have no ff return false; if (clk[cls] == nullptr) clk[cls] = ci->slice_clk; else if (clk[cls] != ci->slice_clk) return false; if (ce[cls] == nullptr) ce[cls] = ci->slice_ce; else if (ce[cls] != ci->slice_ce) return false; if (lsr[cls] == nullptr) lsr[cls] = ci->slice_lsr; else if (lsr[cls] != ci->slice_lsr) return false; if (mode[cls] == IdString()) mode[cls] = ci->ff_type; else if (mode[cls] != ci->ff_type) { auto res = dff_comp_mode.find(mode[cls]); if (res == dff_comp_mode.end() || res->second != ci->ff_type) return false; } } } return true; } void Arch::route_gowin_globals(Context *ctx) { globals_router.route_globals(ctx); } void Arch::mark_gowin_globals(Context *ctx) { globals_router.mark_globals(ctx); } // --------------------------------------------------------------- void Arch::pre_pack(Context *ctx) { if (bool_or_default(settings, id("arch.enable-auto-longwires"))) { auto_longwires(); } } void Arch::post_pack(Context *ctx) {} NEXTPNR_NAMESPACE_END