/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2023 Myrtle Shah * * 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 "pack.h" #include #include #include #include #include #include "chain_utils.h" #include "design_utils.h" #include "extra_data.h" #include "log.h" #include "nextpnr.h" #include "pins.h" #define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" #include "himbaechel_constids.h" NEXTPNR_NAMESPACE_BEGIN // Process the contents of packed_cells void XilinxPacker::flush_cells() { for (auto pcell : packed_cells) { for (auto &port : ctx->cells[pcell]->ports) { ctx->cells[pcell]->disconnectPort(port.first); } ctx->cells.erase(pcell); } packed_cells.clear(); } void XilinxPacker::xform_cell(const dict &rules, CellInfo *ci) { auto &rule = rules.at(ci->type); ci->attrs[id_X_ORIG_TYPE] = ci->type.str(ctx); ci->type = rule.new_type; std::vector orig_port_names; for (auto &port : ci->ports) orig_port_names.push_back(port.first); for (auto pname : orig_port_names) { if (rule.port_multixform.count(pname)) { auto old_port = ci->ports.at(pname); ci->disconnectPort(pname); ci->ports.erase(pname); for (auto new_name : rule.port_multixform.at(pname)) { ci->ports[new_name].name = new_name; ci->ports[new_name].type = old_port.type; ci->connectPort(new_name, old_port.net); ci->attrs[ctx->id("X_ORIG_PORT_" + new_name.str(ctx))] = pname.str(ctx); } } else { IdString new_name; if (rule.port_xform.count(pname)) { new_name = rule.port_xform.at(pname); } else { std::string stripped_name; for (auto c : pname.str(ctx)) if (c != '[' && c != ']') stripped_name += c; new_name = ctx->id(stripped_name); } if (new_name != pname) { ci->renamePort(pname, new_name); } ci->attrs[ctx->id("X_ORIG_PORT_" + new_name.str(ctx))] = pname.str(ctx); } } std::vector xform_params; for (auto ¶m : ci->params) if (rule.param_xform.count(param.first)) xform_params.push_back(param.first); for (auto param : xform_params) ci->params[rule.param_xform.at(param)] = ci->params[param]; for (auto &attr : rule.set_attrs) ci->attrs[attr.first] = attr.second; for (auto ¶m : rule.set_params) ci->params[param.first] = param.second; } void XilinxPacker::generic_xform(const dict &rules, bool print_summary) { std::map cell_count; std::map new_types; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (rules.count(ci->type)) { cell_count[ci->type.str(ctx)]++; xform_cell(rules, ci); new_types[ci->type.str(ctx)]++; } } if (print_summary) { for (auto &nt : new_types) { log_info(" Created %d %s cells from:\n", nt.second, nt.first.c_str()); for (auto &cc : cell_count) { if (rules.at(ctx->id(cc.first)).new_type != ctx->id(nt.first)) continue; log_info(" %6dx %s\n", cc.second, cc.first.c_str()); } } } } CellInfo *XilinxPacker::feed_through_lut(NetInfo *net, const std::vector &feed_users) { NetInfo *feedthru_net = ctx->createNet(ctx->idf("%s$legal%d", net->name.c_str(ctx), ++autoidx)); CellInfo *lut = create_lut(stringf("%s$LUT%d", net->name.c_str(ctx), ++autoidx), {net}, feedthru_net, Property(2)); for (auto &usr : feed_users) { usr.cell->disconnectPort(usr.port); usr.cell->connectPort(usr.port, feedthru_net); } return lut; } CellInfo *XilinxPacker::feed_through_muxf(NetInfo *net, IdString type, const std::vector &feed_users) { NetInfo *feedthru_net = ctx->createNet(ctx->idf("%s$legal$%d", net->name.c_str(ctx), ++autoidx)); CellInfo *mux = create_cell(type, ctx->idf("%s$MUX$%d", net->name.c_str(ctx), ++autoidx)); mux->connectPort(id_I0, net); mux->connectPort(id_O, feedthru_net); mux->connectPort(id_S, ctx->nets[ctx->id("$PACKER_GND_NET")].get()); for (auto &usr : feed_users) { usr.cell->disconnectPort(usr.port); usr.cell->connectPort(usr.port, feedthru_net); } return mux; } IdString XilinxPacker::int_name(IdString base, const std::string &postfix, bool is_hierarchy) { return ctx->id(base.str(ctx) + (is_hierarchy ? "$subcell$" : "$intcell$") + postfix); } NetInfo *XilinxPacker::create_internal_net(IdString base, const std::string &postfix, bool is_hierarchy) { IdString name = ctx->id(base.str(ctx) + (is_hierarchy ? "$subnet$" : "$intnet$") + postfix); return ctx->createNet(name); } void XilinxPacker::pack_luts() { log_info("Packing LUTs..\n"); dict lut_rules; for (int k = 1; k <= 6; k++) { IdString lut = ctx->id("LUT" + std::to_string(k)); lut_rules[lut].new_type = id_SLICE_LUTX; for (int i = 0; i < k; i++) lut_rules[lut].port_xform[ctx->id("I" + std::to_string(i))] = ctx->id("A" + std::to_string(i + 1)); lut_rules[lut].port_xform[id_O] = id_O6; } lut_rules[id_LUT6_2] = XFormRule(lut_rules[id_LUT6]); generic_xform(lut_rules, true); } void XilinxPacker::pack_ffs() { log_info("Packing flipflops..\n"); dict ff_rules; ff_rules[id_FDCE].new_type = id_SLICE_FFX; ff_rules[id_FDCE].port_xform[id_C] = id_CK; ff_rules[id_FDCE].port_xform[id_CLR] = id_SR; // ff_rules[id_FDCE].param_xform[id_IS_CLR_INVERTED] = id_IS_SR_INVERTED; ff_rules[id_FDPE].new_type = id_SLICE_FFX; ff_rules[id_FDPE].port_xform[id_C] = id_CK; ff_rules[id_FDPE].port_xform[id_PRE] = id_SR; // ff_rules[id_FDPE].param_xform[id_IS_PRE_INVERTED] = id_IS_SR_INVERTED; ff_rules[id_FDRE].new_type = id_SLICE_FFX; ff_rules[id_FDRE].port_xform[id_C] = id_CK; ff_rules[id_FDRE].port_xform[id_R] = id_SR; ff_rules[id_FDRE].set_attrs.emplace_back(id_X_FFSYNC, "1"); // ff_rules[id_FDRE].param_xform[id_IS_R_INVERTED] = id_IS_SR_INVERTED; ff_rules[id_FDSE].new_type = id_SLICE_FFX; ff_rules[id_FDSE].port_xform[id_C] = id_CK; ff_rules[id_FDSE].port_xform[id_S] = id_SR; ff_rules[id_FDSE].set_attrs.emplace_back(id_X_FFSYNC, "1"); // ff_rules[id_FDSE].param_xform[id_IS_S_INVERTED] = id_IS_SR_INVERTED; ff_rules[id_FDCE_1] = XFormRule(ff_rules[id_FDCE]); ff_rules[id_FDCE_1].set_params.emplace_back(id_IS_C_INVERTED, 1); ff_rules[id_FDPE_1] = XFormRule(ff_rules[id_FDPE]); ff_rules[id_FDPE_1].set_params.emplace_back(id_IS_C_INVERTED, 1); ff_rules[id_FDRE_1] = XFormRule(ff_rules[id_FDRE]); ff_rules[id_FDRE_1].set_params.emplace_back(id_IS_C_INVERTED, 1); ff_rules[id_FDSE_1] = XFormRule(ff_rules[id_FDSE]); ff_rules[id_FDSE_1].set_params.emplace_back(id_IS_C_INVERTED, 1); generic_xform(ff_rules, true); } void XilinxPacker::pack_lutffs() { int pairs = 0; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->cluster != ClusterId() || !ci->constr_children.empty()) continue; if (ci->type != id_SLICE_FFX) continue; NetInfo *d = ci->getPort(id_D); if (d->driver.cell == nullptr || d->driver.cell->type != id_SLICE_LUTX || d->driver.port != id_O6) continue; CellInfo *lut = d->driver.cell; if (lut->cluster != ClusterId() || !lut->constr_children.empty()) continue; lut->constr_children.push_back(ci); lut->cluster = lut->name; ci->cluster = lut->name; ci->constr_x = 0; ci->constr_y = 0; ci->constr_z = (BEL_FF - BEL_6LUT); ++pairs; } log_info("Constrained %d LUTFF pairs.\n", pairs); } bool XilinxPacker::is_constrained(const CellInfo *cell) { return cell->cluster != ClusterId(); } void XilinxPacker::legalise_muxf_tree(CellInfo *curr, std::vector &mux_roots) { if (curr->type.str(ctx).substr(0, 3) == "LUT") return; for (IdString p : {id_I0, id_I1}) { NetInfo *pn = curr->getPort(p); if (pn == nullptr || pn->driver.cell == nullptr) continue; if (curr->type == id_MUXF7) { if (pn->driver.cell->type.str(ctx).substr(0, 3) != "LUT" || is_constrained(pn->driver.cell)) { PortRef pr; pr.cell = curr; pr.port = p; feed_through_lut(pn, {pr}); continue; } } else { IdString next_type; if (curr->type == id_MUXF9) next_type = id_MUXF8; else if (curr->type == id_MUXF8) next_type = id_MUXF7; else NPNR_ASSERT_FALSE("bad mux type"); if (pn->driver.cell->type != next_type || is_constrained(pn->driver.cell) || bool_or_default(pn->driver.cell->attrs, id_MUX_TREE_ROOT)) { PortRef pr; pr.cell = curr; pr.port = p; feed_through_muxf(pn, next_type, {pr}); continue; } } legalise_muxf_tree(pn->driver.cell, mux_roots); } } void XilinxPacker::constrain_muxf_tree(CellInfo *curr, CellInfo *base, int zoffset) { if (curr->type == id_SLICE_LUTX && (curr->constr_abs_z || curr->cluster != ClusterId())) return; int base_z = 0; if (base->type == id_MUXF7) base_z = BEL_F7MUX; else if (base->type == id_MUXF8) base_z = BEL_F8MUX; else if (base->type == id_MUXF9) base_z = BEL_F9MUX; else if (base->constr_abs_z) base_z = base->constr_z; else NPNR_ASSERT_FALSE("unexpected mux base type"); int curr_z = zoffset * 16; int input_spacing = 0; if (curr->type == id_MUXF7) { curr_z += BEL_F7MUX; input_spacing = 1; } else if (curr->type == id_MUXF8) { curr_z += BEL_F8MUX; input_spacing = 2; } else if (curr->type == id_MUXF9) { curr_z += BEL_F9MUX; input_spacing = 4; } else curr_z += BEL_6LUT; if (curr != base) { curr->constr_x = 0; curr->constr_y = 0; curr->constr_z = curr_z - base_z; curr->constr_abs_z = false; curr->cluster = base->name; base->constr_children.push_back(curr); } if (curr->type.in(id_MUXF7, id_MUXF8, id_MUXF9)) { NetInfo *i0 = curr->getPort(id_I0), *i1 = curr->getPort(id_I1); if (i0 != nullptr && i0->driver.cell != nullptr) constrain_muxf_tree(i0->driver.cell, base, zoffset + input_spacing); if (i1 != nullptr && i1->driver.cell != nullptr) constrain_muxf_tree(i1->driver.cell, base, zoffset); } } void XilinxPacker::pack_muxfs() { log_info("Packing MUX[789]s..\n"); std::vector mux_roots; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); ci->attrs.erase(id_MUX_TREE_ROOT); if (ci->type == id_MUXF9) { log_error("MUXF9 is not supported on xc7!\n"); } else if (ci->type == id_MUXF8) { NetInfo *o = ci->getPort(id_O); if (o == nullptr || o->users.entries() != 1 || (*o->users.begin()).cell->type != id_MUXF9 || is_constrained((*o->users.begin()).cell) || (*o->users.begin()).port == id_S) mux_roots.push_back(ci); } else if (ci->type == id_MUXF7) { NetInfo *o = ci->getPort(id_O); if (o == nullptr || o->users.entries() != 1 || (*o->users.begin()).cell->type != id_MUXF8 || is_constrained((*o->users.begin()).cell) || (*o->users.begin()).port == id_S) mux_roots.push_back(ci); } } for (auto root : mux_roots) root->attrs[id_MUX_TREE_ROOT] = 1; for (auto root : mux_roots) legalise_muxf_tree(root, mux_roots); for (auto root : mux_roots) { root->cluster = root->name; constrain_muxf_tree(root, root, 0); } } void XilinxPacker::finalise_muxfs() { dict muxf_rules; muxf_rules[id_MUXF9].new_type = id_F9MUX; muxf_rules[id_MUXF9].port_xform[id_I0] = id_0; muxf_rules[id_MUXF9].port_xform[id_I1] = id_1; muxf_rules[id_MUXF9].port_xform[id_S] = id_S0; muxf_rules[id_MUXF9].port_xform[id_O] = id_OUT; muxf_rules[id_MUXF8].new_type = id_SELMUX2_1; muxf_rules[id_MUXF8].port_xform = muxf_rules[id_MUXF9].port_xform; muxf_rules[id_MUXF7].new_type = id_SELMUX2_1; muxf_rules[id_MUXF7].port_xform = muxf_rules[id_MUXF9].port_xform; generic_xform(muxf_rules, true); } void XilinxPacker::pack_srls() { dict srl_rules; srl_rules[id_SRL16E].new_type = id_SLICE_LUTX; srl_rules[id_SRL16E].port_xform[id_CLK] = id_CLK; srl_rules[id_SRL16E].port_xform[id_CE] = id_WE; srl_rules[id_SRL16E].port_xform[id_D] = id_DI2; srl_rules[id_SRL16E].port_xform[id_Q] = id_O6; srl_rules[id_SRL16E].set_attrs.emplace_back(id_X_LUT_AS_SRL, "1"); srl_rules[id_SRLC32E].new_type = id_SLICE_LUTX; srl_rules[id_SRLC32E].port_xform[id_CLK] = id_CLK; srl_rules[id_SRLC32E].port_xform[id_CE] = id_WE; srl_rules[id_SRLC32E].port_xform[id_D] = id_DI1; srl_rules[id_SRLC32E].port_xform[id_Q] = id_O6; srl_rules[id_SRLC32E].set_attrs.emplace_back(id_X_LUT_AS_SRL, "1"); // FIXME: Q31 support generic_xform(srl_rules, true); // Fixup SRL inputs for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type != id_SLICE_LUTX) continue; std::string orig_type = str_or_default(ci->attrs, id_X_ORIG_TYPE); if (orig_type == "SRL16E") { for (int i = 3; i >= 0; i--) { ci->renamePort(ctx->id("A" + std::to_string(i)), ctx->id("A" + std::to_string(i + 2))); } for (auto tp : {id_A1, id_A6}) { ci->ports[tp].name = tp; ci->ports[tp].type = PORT_IN; ci->connectPort(tp, ctx->nets[ctx->id("$PACKER_VCC_NET")].get()); } } else if (orig_type == "SRLC32E") { for (int i = 4; i >= 0; i--) { ci->renamePort(ctx->id("A" + std::to_string(i)), ctx->id("A" + std::to_string(i + 2))); } for (auto tp : {id_A1}) { ci->ports[tp].name = tp; ci->ports[tp].type = PORT_IN; ci->connectPort(tp, ctx->nets[ctx->id("$PACKER_VCC_NET")].get()); } } } } void XilinxPacker::pack_constants() { log_info("Packing constants..\n"); if (tied_pins.empty()) get_tied_pins(ctx, tied_pins); if (invertible_pins.empty()) get_invertible_pins(ctx, invertible_pins); if (!ctx->cells.count(ctx->id("$PACKER_GND_DRV"))) { CellInfo *gnd_cell = ctx->createCell(ctx->id("$PACKER_GND_DRV"), id_PSEUDO_GND); gnd_cell->addOutput(id_Y); NetInfo *gnd_net = ctx->createNet(ctx->id("$PACKER_GND_NET")); gnd_net->constant_value = id_GND; gnd_cell->connectPort(id_Y, gnd_net); CellInfo *vcc_cell = ctx->createCell(ctx->id("$PACKER_VCC_DRV"), id_PSEUDO_VCC); vcc_cell->addOutput(id_Y); NetInfo *vcc_net = ctx->createNet(ctx->id("$PACKER_VCC_NET")); vcc_net->constant_value = id_VCC; vcc_cell->connectPort(id_Y, vcc_net); } NetInfo *gnd = ctx->nets[ctx->id("$PACKER_GND_NET")].get(), *vcc = ctx->nets[ctx->id("$PACKER_VCC_NET")].get(); std::vector dead_nets; std::vector> const_ports; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (!tied_pins.count(ci->type)) continue; auto &tp = tied_pins.at(ci->type); for (auto port : tp) { if (cell.second->ports.count(port.first) && cell.second->ports.at(port.first).net != nullptr && cell.second->ports.at(port.first).net->driver.cell != nullptr) continue; const_ports.emplace_back(ci, port.first, port.second); } } for (auto &net : ctx->nets) { NetInfo *ni = net.second.get(); if (ni->driver.cell != nullptr && ni->driver.cell->type == id_GND) { IdString drv_cell = ni->driver.cell->name; for (auto &usr : ni->users) { const_ports.emplace_back(usr.cell, usr.port, false); usr.cell->ports.at(usr.port).net = nullptr; } dead_nets.push_back(net.first); ctx->cells.erase(drv_cell); } else if (ni->driver.cell != nullptr && ni->driver.cell->type == id_VCC) { IdString drv_cell = ni->driver.cell->name; for (auto &usr : ni->users) { const_ports.emplace_back(usr.cell, usr.port, true); usr.cell->ports.at(usr.port).net = nullptr; } dead_nets.push_back(net.first); ctx->cells.erase(drv_cell); } } for (auto port : const_ports) { CellInfo *ci; IdString pname; bool cval; std::tie(ci, pname, cval) = port; if (!ci->ports.count(pname)) { ci->addInput(pname); } if (ci->ports.at(pname).net != nullptr) { // Case where a port with a default tie value is previously connected to an undriven net NPNR_ASSERT(ci->ports.at(pname).net->driver.cell == nullptr); ci->disconnectPort(pname); } if (!cval && invertible_pins.count(ci->type) && invertible_pins.at(ci->type).count(pname)) { // Invertible pins connected to zero are optimised to a connection to Vcc (which is easier to route) // and an inversion ci->params[ctx->idf("IS_%s_INVERTED", pname.c_str(ctx))] = Property(1); cval = true; } ci->connectPort(pname, cval ? vcc : gnd); } for (auto dn : dead_nets) { ctx->nets.erase(dn); } } void XilinxPacker::rename_net(IdString old, IdString newname) { std::unique_ptr ni; std::swap(ni, ctx->nets[old]); ctx->nets.erase(old); ni->name = newname; ctx->nets[newname] = std::move(ni); } void XilinxPacker::tie_port(CellInfo *ci, const std::string &port, bool value, bool inv) { IdString p = ctx->id(port); if (!ci->ports.count(p)) { ci->addInput(p); } if (value || inv) ci->connectPort(p, ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get()); else ci->connectPort(p, ctx->nets.at(ctx->id("$PACKER_GND_NET")).get()); if (!value && inv) ci->params[ctx->idf("IS_%s_INVERTED", port.c_str())] = Property(1); } void XC7Packer::pack_bram() { log_info("Packing BRAM..\n"); // Rules for normal TDP BRAM dict bram_rules; bram_rules[id_RAMB18E1].new_type = id_RAMB18E1_RAMB18E1; bram_rules[id_RAMB18E1].port_multixform[ctx->id("WEA[0]")] = {id_WEA0, id_WEA1}; bram_rules[id_RAMB18E1].port_multixform[ctx->id("WEA[1]")] = {id_WEA2, id_WEA3}; bram_rules[id_RAMB36E1].new_type = id_RAMB36E1_RAMB36E1; // Some ports have upper/lower bel pins in 36-bit mode std::vector>> ul_pins; get_bram36_ul_pins(ctx, ul_pins); for (auto &ul : ul_pins) { for (auto &bp : ul.second) bram_rules[id_RAMB36E1].port_multixform[ul.first].push_back(ctx->id(bp)); } bram_rules[id_RAMB36E1].port_multixform[ctx->id("ADDRARDADDR[15]")].push_back(id_ADDRARDADDRL15); bram_rules[id_RAMB36E1].port_multixform[ctx->id("ADDRBWRADDR[15]")].push_back(id_ADDRBWRADDRL15); // Special rules for SDP rules, relating to WE connectivity dict sdp_bram_rules = bram_rules; for (int i = 0; i < 4; i++) { // Connects to two WEBWE bel pins sdp_bram_rules[id_RAMB18E1].port_multixform[ctx->idf("WEBWE[%d]", i)].push_back(ctx->idf("WEBWE%d", i * 2)); sdp_bram_rules[id_RAMB18E1].port_multixform[ctx->idf("WEBWE[%d]", i)].push_back(ctx->idf("WEBWE%d", i * 2 + 1)); // Not used in SDP mode sdp_bram_rules[id_RAMB18E1].port_multixform[ctx->idf("WEA[%d]", i)] = {}; } for (int i = 0; i < 8; i++) { sdp_bram_rules[id_RAMB36E1].port_multixform[ctx->idf("WEBWE[%d]", i)].clear(); // Connects to two WEBWE bel pins sdp_bram_rules[id_RAMB36E1].port_multixform[ctx->idf("WEBWE[%d]", i)].push_back(ctx->idf("WEBWEL%d", i)); sdp_bram_rules[id_RAMB36E1].port_multixform[ctx->idf("WEBWE[%d]", i)].push_back(ctx->idf("WEBWEU%d", i)); // Not used in SDP mode sdp_bram_rules[id_RAMB36E1].port_multixform[ctx->idf("WEA[%d]", i)] = {}; } // 72-bit BRAMs: drop upper bits of WEB in TDP mode for (int i = 4; i < 8; i++) bram_rules[id_RAMB36E1].port_multixform[ctx->idf("WEBWE[%d]", i)] = {}; // Process SDP BRAM first for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if ((ci->type == id_RAMB18E1 && int_or_default(ci->params, ctx->id("WRITE_WIDTH_B"), 0) == 36) || (ci->type == id_RAMB36E1 && int_or_default(ci->params, ctx->id("WRITE_WIDTH_B"), 0) == 72)) xform_cell(sdp_bram_rules, ci); } // Rewrite byte enables according to data width for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type.in(id_RAMB18E1, id_RAMB36E1)) { for (char port : {'A', 'B'}) { int write_width = int_or_default(ci->params, ctx->idf("WRITE_WIDTH_%c", port), 18); int we_width; if (ci->type == id_RAMB36E1) we_width = 4; else we_width = (port == 'B') ? 4 : 2; if (write_width >= (9 * we_width)) continue; int used_we_width = std::max(write_width / 9, 1); for (int i = used_we_width; i < we_width; i++) { NetInfo *low_we = ci->getPort(ctx->id(std::string(port == 'B' ? "WEBWE[" : "WEA[") + std::to_string(i % used_we_width) + "]")); IdString curr_we = ctx->id(std::string(port == 'B' ? "WEBWE[" : "WEA[") + std::to_string(i) + "]"); if (!ci->ports.count(curr_we)) { ci->ports[curr_we].type = PORT_IN; ci->ports[curr_we].name = curr_we; } ci->disconnectPort(curr_we); ci->connectPort(curr_we, low_we); } } } } generic_xform(bram_rules, false); // These pins have no logical mapping, so must be tied after transformation for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type == id_RAMB18E1_RAMB18E1) { int wwa = int_or_default(ci->params, id_WRITE_WIDTH_A, 0); for (int i = ((wwa == 0) ? 0 : 2); i < 4; i++) { IdString port = ctx->idf("WEA%d", i); if (!ci->ports.count(port)) { ci->ports[port].name = port; ci->ports[port].type = PORT_IN; ci->connectPort(port, ctx->nets[ctx->id("$PACKER_GND_NET")].get()); } } int wwb = int_or_default(ci->params, id_WRITE_WIDTH_B, 0); if (wwb != 36) { for (int i = 4; i < 8; i++) { IdString port = ctx->id("WEBWE" + std::to_string(i)); if (!ci->ports.count(port)) { ci->ports[port].name = port; ci->ports[port].type = PORT_IN; ci->connectPort(port, ctx->nets[ctx->id("$PACKER_GND_NET")].get()); } } } for (auto p : {id_ADDRATIEHIGH0, id_ADDRATIEHIGH1, id_ADDRBTIEHIGH0, id_ADDRBTIEHIGH1}) { if (!ci->ports.count(p)) { ci->ports[p].name = p; ci->ports[p].type = PORT_IN; } else { ci->disconnectPort(p); } ci->connectPort(p, ctx->nets[ctx->id("$PACKER_VCC_NET")].get()); } } else if (ci->type == id_RAMB36E1_RAMB36E1) { for (auto p : {id_ADDRARDADDRL15, id_ADDRBWRADDRL15}) { if (!ci->ports.count(p)) { ci->ports[p].name = p; ci->ports[p].type = PORT_IN; } else { ci->disconnectPort(p); } ci->connectPort(p, ctx->nets[ctx->id("$PACKER_VCC_NET")].get()); } if (int_or_default(ci->params, id_WRITE_WIDTH_A, 0) == 1) { ci->disconnectPort(id_DIADI1); ci->connectPort(id_DIADI1, ci->getPort(id_DIADI0)); ci->attrs[id_X_ORIG_PORT_DIADI1] = std::string("DIADI[0]"); ci->disconnectPort(id_DIPADIP0); ci->disconnectPort(id_DIPADIP1); } if (int_or_default(ci->params, id_WRITE_WIDTH_B, 0) == 1) { ci->disconnectPort(id_DIBDI1); ci->connectPort(id_DIBDI1, ci->getPort(id_DIBDI0)); ci->attrs[id_X_ORIG_PORT_DIBDI1] = std::string("DIBDI[0]"); ci->disconnectPort(id_DIPBDIP0); ci->disconnectPort(id_DIPBDIP1); } if (int_or_default(ci->params, id_WRITE_WIDTH_B, 0) != 72) { for (std::string s : {"L", "U"}) { for (int i = 4; i < 8; i++) { IdString port = ctx->idf("WEBWE%s%d", s.c_str(), i); if (!ci->ports.count(port)) { ci->ports[port].name = port; ci->ports[port].type = PORT_IN; ci->connectPort(port, ctx->nets[ctx->id("$PACKER_GND_NET")].get()); } } } } else { // Tie WEA low for (std::string s : {"L", "U"}) { for (int i = 0; i < 4; i++) { IdString port = ctx->idf("WEA%s%d", s.c_str(), i); if (!ci->ports.count(port)) { ci->ports[port].name = port; ci->ports[port].type = PORT_IN; ci->connectPort(port, ctx->nets[ctx->id("$PACKER_GND_NET")].get()); } } } } } } } void XilinxPacker::pack_inverters() { // FIXME: fold where possible for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type == id_INV) { ci->params[id_INIT] = Property(1, 2); ci->renamePort(id_I, id_I0); ci->type = id_LUT1; } } } void XilinxImpl::pack() { const ArchArgs &args = ctx->args; if (args.options.count("xdc")) { parse_xdc(args.options.at("xdc")); } XC7Packer packer(ctx, this); packer.pack_constants(); packer.pack_inverters(); packer.pack_io(); packer.prepare_clocking(); packer.pack_constants(); packer.pack_iologic(); packer.pack_idelayctrl(); packer.pack_clocking(); packer.pack_muxfs(); packer.pack_carries(); packer.pack_srls(); packer.pack_luts(); packer.pack_dram(); packer.pack_bram(); packer.pack_dsps(); packer.pack_ffs(); packer.finalise_muxfs(); packer.pack_lutffs(); } NEXTPNR_NAMESPACE_END