/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2023 gatecat * * 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 "himbaechel_api.h" #include "log.h" #include "nextpnr.h" #include "util.h" #include "placer_heap.h" #include "placer_static.h" #include "xilinx.h" #include "himbaechel_helpers.h" #define GEN_INIT_CONSTIDS #define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" #include "himbaechel_constids.h" NEXTPNR_NAMESPACE_BEGIN struct FFControlSet { unsigned flags = 0; enum { IS_LATCH = 1, IS_CLKINV = 2, IS_SRINV = 4, FFSYNC = 8, }; IdString clk, sr, ce; bool operator==(const FFControlSet &other) const { return flags == other.flags && clk == other.clk && ce == other.ce && sr == other.sr; }; unsigned hash() const { unsigned hash = mkhash(clk.hash(), sr.hash()); hash = mkhash(hash, ce.hash()); hash = mkhash(hash, flags); return hash; } }; XilinxImpl::~XilinxImpl() {}; po::options_description XilinxImpl::getUArchOptions() { po::options_description specific("Xilinx specific options"); specific.add_options()("fasm", po::value(), "fasm bitstream output file"); specific.add_options()("xdc", po::value(), "name of constraints file"); return specific; } void XilinxImpl::init_database(Arch *arch) { const ArchArgs &args = arch->args; init_uarch_constids(arch); std::smatch match; std::regex devicere = std::regex("(xc7[azkv]\\d+t?)([a-z0-9]+)-(\\dL?)"); if (!std::regex_match(args.device, match, devicere)) { log_error("Invalid device %s\n", args.device.c_str()); } std::string die = match[1].str(); if (die == "xc7a35t") die = "xc7a50t"; arch->load_chipdb(stringf("xilinx/chipdb-%s.bin", die.c_str())); std::string package = match[2].str(); arch->set_package(package); arch->set_speed_grade("DEFAULT"); } void XilinxImpl::init(Context *ctx) { h.init(ctx); HimbaechelAPI::init(ctx); tile_status.resize(ctx->chip_info->tile_insts.size()); for (int i = 0; i < ctx->chip_info->tile_insts.ssize(); i++) { auto extra_data = tile_extra_data(i); tile_status.at(i).site_variant.resize(extra_data->sites.ssize()); } } SiteIndex XilinxImpl::get_bel_site(BelId bel) const { auto &bel_data = chip_bel_info(ctx->chip_info, bel); auto site_key = BelSiteKey::unpack(bel_data.site); return SiteIndex(bel.tile, site_key.site); } IdString XilinxImpl::get_site_name(SiteIndex site) const { const auto &site_data = tile_extra_data(site.tile)->sites[site.site]; return ctx->idf("%s_X%dY%d", IdString(site_data.name_prefix).c_str(ctx), site_data.site_x, site_data.site_y); } BelId XilinxImpl::get_site_bel(SiteIndex site, IdString bel_name) const { const auto &tile_data = chip_tile_info(ctx->chip_info, site.tile); for (int32_t i = 0; i < tile_data.bels.ssize(); i++) { const auto &bel_data = tile_data.bels[i]; if (BelSiteKey::unpack(bel_data.site).site != site.site) continue; if (reinterpret_cast(bel_data.extra_data.get())->name_in_site != bel_name.index) continue; return BelId(site.tile, i); } return BelId(); } IdString XilinxImpl::bel_name_in_site(BelId bel) const { const auto &bel_data = chip_bel_info(ctx->chip_info, bel); return IdString(reinterpret_cast(bel_data.extra_data.get())->name_in_site); } IdStringList XilinxImpl::get_site_bel_name(BelId bel) const { return IdStringList::concat(get_site_name(get_bel_site(bel)), bel_name_in_site(bel)); } WireId XilinxImpl::lookup_wire(int tile, IdString wire_name) const { const auto &tdata = chip_tile_info(ctx->chip_info, tile); for (int wire = 0; wire < tdata.wires.ssize(); wire++) { if (IdString(tdata.wires[wire].name) == wire_name) return ctx->normalise_wire(tile, wire); } return WireId(); } void XilinxImpl::notifyBelChange(BelId bel, CellInfo *cell) { auto &ts = tile_status.at(bel.tile); auto &bel_data = chip_bel_info(ctx->chip_info, bel); auto site_key = BelSiteKey::unpack(bel_data.site); // Update bound site variant for pip validity use later on if (cell && cell->type != id_PAD && site_key.site >= 0 && site_key.site < int(ts.site_variant.size())) { ts.site_variant.at(site_key.site) = site_key.site_variant; } if (is_logic_tile(bel)) update_logic_bel(bel, cell); if (is_bram_tile(bel)) update_bram_bel(bel, cell); } void XilinxImpl::update_logic_bel(BelId bel, CellInfo *cell) { int z = ctx->getBelLocation(bel).z; NPNR_ASSERT(z < 128); auto &tts = tile_status.at(bel.tile); if (!tts.lts) tts.lts = std::make_unique(); auto &ts = *(tts.lts); auto tags = get_tags(cell), last_tags = get_tags(ts.cells[z]); if ((z == ((3 << 4) | BEL_6LUT)) || (z == ((3 << 4) | BEL_5LUT))) { if ((tags && tags->lut.is_memory) || (last_tags && last_tags->lut.is_memory)) { // Special case - memory write port invalidates everything for (int i = 0; i < 8; i++) ts.eights[i].dirty = true; // if (xc7) ts.halfs[0].dirty = true; // WCLK and CLK0 shared } } if ((((z & 0xF) == BEL_6LUT) || ((z & 0xF) == BEL_5LUT)) && ((tags && tags->lut.is_srl) || (last_tags && last_tags->lut.is_srl))) { // SRLs invalidate everything due to write clock for (int i = 0; i < 8; i++) ts.eights[i].dirty = true; // if (xc7) ts.halfs[0].dirty = true; // WCLK and CLK0 shared } ts.cells[z] = cell; // determine which sections to mark as dirty switch (z & 0xF) { case BEL_FF: case BEL_FF2: ts.halfs[(z >> 4) / 4].dirty = true; if ((((z >> 4) / 4) == 0) /*&& xc7*/) ts.eights[3].dirty = true; /* fall-through */ case BEL_6LUT: case BEL_5LUT: ts.eights[z >> 4].dirty = true; break; case BEL_F7MUX: ts.eights[z >> 4].dirty = true; ts.eights[(z >> 4) + 1].dirty = true; break; case BEL_F8MUX: ts.eights[(z >> 4) + 1].dirty = true; ts.eights[(z >> 4) + 2].dirty = true; break; case BEL_CARRY4: for (int i = ((z >> 4) / 4) * 4; i < (((z >> 4) / 4) + 1) * 4; i++) ts.eights[i].dirty = true; break; } } void XilinxImpl::update_bram_bel(BelId bel, CellInfo *cell) { IdString type = ctx->getBelType(bel); if (!type.in(id_RAMBFIFO18E2_RAMBFIFO18E2, id_RAMBFIFO36E2_RAMBFIFO36E2, id_RAMB18E2_RAMB18E2, id_FIFO36E2_FIFO36E2, id_RAMBFIFO36E1_RAMBFIFO36E1, id_RAMB36E1_RAMB36E1, id_RAMB18E1_RAMB18E1)) return; auto &tts = tile_status.at(bel.tile); if (!tts.bts) tts.bts = std::make_unique(); int z = ctx->getBelLocation(bel).z; NPNR_ASSERT(z >= 0 && z < 12); tts.bts->cells[z] = cell; } bool XilinxImpl::is_pip_unavail(PipId pip) const { const auto &pip_data = chip_pip_info(ctx->chip_info, pip); const auto &extra_data = *reinterpret_cast(pip_data.extra_data.get()); unsigned pip_type = pip_data.flags; if (pip_type == PIP_SITE_ENTRY) { WireId dst = ctx->getPipDstWire(pip); if (ctx->getWireType(dst) == id_INTENT_SITE_GND) { const auto <s = tile_status[dst.tile].lts; if (lts && (lts->cells[BEL_5LUT] != nullptr || lts->cells[BEL_6LUT] != nullptr)) return true; // Ground driver only available if lowest 5LUT and 6LUT not used } } else if (pip_type == PIP_CONST_DRIVER) { WireId dst = ctx->getPipDstWire(pip); const auto <s = tile_status[dst.tile].lts; if (lts && (lts->cells[BEL_5LUT] != nullptr || lts->cells[BEL_6LUT] != nullptr)) return true; // Ground driver only available if lowest 5LUT and 6LUT not used } else if (pip_type == PIP_SITE_INTERNAL) { if (extra_data.bel_name == ID_TRIBUF) return true; auto site = BelSiteKey::unpack(extra_data.site_key); // Check site variant of PIP matches configured site variant of tile if (site.site >= 0 && site.site < int(tile_status[pip.tile].site_variant.size())) { if (site.site_variant > 0 && site.site_variant != tile_status[pip.tile].site_variant.at(site.site)) return true; } } else if (pip_type == PIP_LUT_PERMUTATION) { const auto <s = tile_status[pip.tile].lts; if (!lts) return false; int eight = (extra_data.pip_config >> 8) & 0xF; if (((extra_data.pip_config >> 4) & 0xF) == (extra_data.pip_config & 0xF)) return false; // from==to, always valid auto lut6 = get_tags(lts->cells[(eight << 4) | BEL_6LUT]); if (lut6 && (lut6->lut.is_memory || lut6->lut.is_srl)) return true; auto lut5 = get_tags(lts->cells[(eight << 4) | BEL_5LUT]); if (lut5 && (lut5->lut.is_memory || lut5->lut.is_srl)) return true; } else if (pip_type == PIP_LUT_ROUTETHRU) { int eight = (extra_data.pip_config >> 8) & 0xF; int dest = (extra_data.pip_config & 0x1); if (eight == 0) return true; // FIXME: conflict with ground if (dest & 0x1) return true; // FIXME: routethru to MUX const auto <s = tile_status[pip.tile].lts; if (!lts) return false; const CellInfo *lut6 = lts->cells[(eight << 4) | BEL_6LUT]; if (lut6) return true; const CellInfo *lut5 = lts->cells[(eight << 4) | BEL_5LUT]; if (lut5) return true; } return false; } void XilinxImpl::prePlace() { assign_cell_tags(); index_control_sets(); } void XilinxImpl::postPlace() { fixup_placement(); ctx->assignArchInfo(); } void XilinxImpl::configurePlacerHeap(PlacerHeapCfg &cfg) { cfg.hpwl_scale_x = 2; cfg.hpwl_scale_y = 1; cfg.beta = 0.5; cfg.placeAllAtOnce = true; cfg.get_cell_legalisation_weight = [this](Context *, CellInfo *ci) { if (ci->type != id_SLICE_LUTX) return 1; auto tags = get_tags(ci); // Place memory first, because they require entire SLICEMs return tags->lut.is_memory ? 100 : 1; }; cfg.ff_bel_bucket = id_SLICE_FFX; cfg.ff_control_set_groups.resize(2); for (int z = 0; z < 8; z++) { cfg.ff_control_set_groups.at(z / 4).push_back((z << 4) | BEL_FF); cfg.ff_control_set_groups.at(z / 4).push_back((z << 4) | BEL_FF2); } cfg.ctrl_set_max_radius = std::vector{18, 15, 12, 9, 6, 3}; cfg.get_cell_control_set = [this](Context *, const CellInfo *ci) { if (ci->type != id_SLICE_FFX) return -1; auto tags = get_tags(ci); return tags->ff.control_set; }; } void XilinxImpl::configurePlacerStatic(PlacerStaticCfg &cfg) { cfg.hpwl_scale_x = 2; cfg.hpwl_scale_y = 1; cfg.glbBufTypes.insert(id_PSEUDO_GND); cfg.glbBufTypes.insert(id_PSEUDO_VCC); cfg.glbBufTypes.insert(id_BUFGCTRL); cfg.glbBufTypes.insert(id_BUFG_BUFG); { cfg.cell_groups.emplace_back(); auto &comb = cfg.cell_groups.back(); comb.name = ctx->id("COMB"); comb.bel_area[id_SLICE_LUTX] = StaticRect(1.0f, 0.125f); comb.bel_area[id_CARRY4] = StaticRect(0.0f, 0.0f); comb.bel_area[id_SELMUX2_1] = StaticRect(0.0f, 0.0f); comb.cell_area[id_SLICE_LUTX] = StaticRect(1.0f, 0.125f); comb.cell_area[id_CARRY4] = StaticRect(1.0f, 0.5f); comb.cell_area[id_SELMUX2_1] = StaticRect(1.0f, 0.125f); comb.zero_area_cells.insert(id_CARRY4); comb.zero_area_cells.insert(id_SELMUX2_1); comb.spacer_rect = StaticRect(1.0f, 0.125f); } { cfg.cell_groups.emplace_back(); auto &comb = cfg.cell_groups.back(); comb.name = ctx->id("FF"); comb.cell_area[id_SLICE_FFX] = StaticRect(1.0f, 0.125f); comb.bel_area[id_SLICE_FFX] = StaticRect(1.0f, 0.125f); comb.spacer_rect = StaticRect(1.0f, 0.125f); } { cfg.cell_groups.emplace_back(); auto &comb = cfg.cell_groups.back(); comb.name = ctx->id("RAM"); comb.cell_area[id_RAMB18E1_RAMB18E1] = StaticRect(1.0f, 3.0f); comb.bel_area[id_RAMB18E1_RAMB18E1] = StaticRect(1.0f, 3.0f); comb.cell_area[id_RAMB36E1_RAMB36E1] = StaticRect(1.0f, 6.0f); comb.bel_area[id_RAMB36E1_RAMB36E1] = StaticRect(0.0f, 0.0f); comb.spacer_rect = StaticRect(1.0f, 3.0f); } { cfg.cell_groups.emplace_back(); auto &comb = cfg.cell_groups.back(); comb.name = ctx->id("DSP"); comb.cell_area[id_DSP48E1_DSP48E1] = StaticRect(1.0f, 3.0f); comb.bel_area[id_DSP48E1_DSP48E1] = StaticRect(1.0f, 3.0f); comb.spacer_rect = StaticRect(1.0f, 3.0f); } cfg.get_cell_area_override = [this](Context *ctx, const CellInfo *ci) -> std::optional { if (ci->type != id_SLICE_LUTX) return {}; auto tags = get_tags(ci); if (tags->lut.is_memory || (ci->cluster != ClusterId() && ctx->getClusterRootCell(ci->cluster)->type == id_CARRY4)) { // macro LUTs use either a half or whole LUT, always return {(tags->lut.input_count == 6) ? StaticRect(1.0f, 0.125f) : StaticRect(1.0f, 0.0625f)}; } else { switch (tags->lut.input_count) { // sliding scale, smaller LUTs pack better case 6: return {StaticRect(1.0f, 0.125f)}; case 5: return {StaticRect(1.0f, 0.09f)}; case 4: return {StaticRect(1.0f, 0.07f)}; case 3: return {StaticRect(1.0f, 0.06f)}; case 2: return {StaticRect(1.0f, 0.04f)}; case 1: return {StaticRect(1.0f, 0.03f)}; default: NPNR_ASSERT_FALSE("unhandled LUT input count"); } } }; } void XilinxImpl::preRoute() { find_source_sink_locs(); route_clocks(); } void XilinxImpl::postRoute() { fixup_routing(); ctx->assignArchInfo(); const ArchArgs &args = ctx->args; if (args.options.count("fasm")) { write_fasm(args.options["fasm"].as()); } } IdString XilinxImpl::bel_tile_type(BelId bel) const { return IdString(chip_tile_info(ctx->chip_info, bel.tile).type_name); } bool XilinxImpl::is_logic_tile(BelId bel) const { return bel_tile_type(bel).in(id_CLEL_L, id_CLEL_R, id_CLEM, id_CLEM_R, id_CLBLL_L, id_CLBLL_R, id_CLBLM_L, id_CLBLM_R); } bool XilinxImpl::is_bram_tile(BelId bel) const { return bel_tile_type(bel).in(id_BRAM, id_BRAM_L, id_BRAM_R); } const XlnxTileInstExtraDataPOD *XilinxImpl::tile_extra_data(int tile) const { return reinterpret_cast(ctx->chip_info->tile_insts[tile].extra_data.get()); } std::string XilinxImpl::tile_name(int tile) const { const auto &data = *tile_extra_data(tile); return stringf("%s_X%dY%d", IdString(data.name_prefix).c_str(ctx), data.tile_x, data.tile_y); } Loc XilinxImpl::rel_site_loc(SiteIndex site) const { const auto &site_data = tile_extra_data(site.tile)->sites[site.site]; return Loc(site_data.rel_x, site_data.rel_y, 0); } SiteIndex XilinxImpl::rel_site(SiteIndex site, int dx, int dy) const { const auto &base_site_data = tile_extra_data(site.tile)->sites[site.site]; for (size_t i = 0; i < tile_extra_data(site.tile)->sites.size(); i++) { const auto &site_data = tile_extra_data(site.tile)->sites[i]; if (site_data.name_prefix == base_site_data.name_prefix && site_data.rel_x == (base_site_data.rel_x + dx) && site_data.rel_y == (base_site_data.rel_y + dy)) return SiteIndex(site.tile, i); } return SiteIndex(); } int XilinxImpl::hclk_for_iob(BelId pad) const { std::string tile_type = bel_tile_type(pad).str(ctx); int ioi = pad.tile; if (boost::starts_with(tile_type, "LIOB")) ioi += 1; else if (boost::starts_with(tile_type, "RIOB")) ioi -= 1; else NPNR_ASSERT_FALSE("unknown IOB side"); return hclk_for_ioi(ioi); } int XilinxImpl::hclk_for_ioi(int tile) const { WireId ioclk0; auto &td = chip_tile_info(ctx->chip_info, tile); for (int i = 0; i < td.wires.ssize(); i++) { std::string name = IdString(td.wires[i].name).str(ctx); if (name == "IOI_IOCLK0" || name == "IOI_SING_IOCLK0") { ioclk0 = ctx->normalise_wire(tile, i); break; } } NPNR_ASSERT(ioclk0 != WireId()); for (auto uh : ctx->getPipsUphill(ioclk0)) return uh.tile; NPNR_ASSERT_FALSE("failed to find HCLK pips"); } void XilinxImpl::assign_cell_tags() { cell_tags.resize(ctx->cells.size()); for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); auto &ct = cell_tags.at(ci->flat_index); if (ci->type == id_SLICE_LUTX) { ct.lut.input_count = 0; for (IdString a : {id_A1, id_A2, id_A3, id_A4, id_A5, id_A6}) { NetInfo *pn = ci->getPort(a); if (pn != nullptr) ct.lut.input_sigs[ct.lut.input_count++] = pn; } ct.lut.output_count = 0; for (IdString o : {id_O6, id_O5}) { NetInfo *pn = ci->getPort(o); if (pn != nullptr) ct.lut.output_sigs[ct.lut.output_count++] = pn; } for (int i = ct.lut.output_count; i < 2; i++) ct.lut.output_sigs[i] = nullptr; ct.lut.di1_net = ci->getPort(id_DI1); ct.lut.di2_net = ci->getPort(id_DI2); ct.lut.wclk = ci->getPort(id_CLK); ct.lut.memory_group = 0; // fixme ct.lut.is_srl = ci->attrs.count(id_X_LUT_AS_SRL); ct.lut.is_memory = ci->attrs.count(id_X_LUT_AS_DRAM); ct.lut.only_drives_carry = false; if (ci->cluster != ClusterId() && ct.lut.output_count > 0 && ct.lut.output_sigs[0] != nullptr && ct.lut.output_sigs[0]->users.entries() == 1 && (*ct.lut.output_sigs[0]->users.begin()).cell->type == id_CARRY4) ct.lut.only_drives_carry = true; const IdString addr_msb_sigs[] = {id_WA7, id_WA8, id_WA9}; for (int i = 0; i < 3; i++) ct.lut.address_msb[i] = ci->getPort(addr_msb_sigs[i]); } else if (ci->type == id_SLICE_FFX) { ct.ff.d = ci->getPort(id_D); ct.ff.clk = ci->getPort(id_CK); ct.ff.ce = ci->getPort(id_CE); ct.ff.sr = ci->getPort(id_SR); ct.ff.is_clkinv = bool_or_default(ci->params, id_IS_CLK_INVERTED, false); ct.ff.is_srinv = bool_or_default(ci->params, id_IS_R_INVERTED, false) || bool_or_default(ci->params, id_IS_S_INVERTED, false) || bool_or_default(ci->params, id_IS_CLR_INVERTED, false) || bool_or_default(ci->params, id_IS_PRE_INVERTED, false); ct.ff.is_latch = ci->attrs.count(id_X_FF_AS_LATCH); ct.ff.ffsync = ci->attrs.count(id_X_FFSYNC); } else if (ci->type.in(id_F7MUX, id_F8MUX, id_F9MUX, id_SELMUX2_1)) { ct.mux.sel = ci->getPort(id_S0); ct.mux.out = ci->getPort(id_OUT); } else if (ci->type == id_CARRY4) { for (int i = 0; i < 4; i++) { ct.carry.out_sigs[i] = ci->getPort(ctx->idf("O%d", i)); ct.carry.cout_sigs[i] = ci->getPort(ctx->idf("CO%d", i)); ct.carry.x_sigs[i] = nullptr; } ct.carry.x_sigs[0] = ci->getPort(id_CYINIT); } else if (ci->type == id_RAMB18E1_RAMB18E1 || ci->type == id_RAMB36E1_RAMB36E1) { bool read_sdp = ((ci->type == id_RAMB18E1_RAMB18E1 && int_or_default(ci->params, ctx->id("READ_WIDTH_B"), 0) == 36) || (ci->type == id_RAMB36E1_RAMB36E1 && int_or_default(ci->params, ctx->id("READ_WIDTH_B"), 0) == 72)); bool write_sdp = ((ci->type == id_RAMB18E1_RAMB18E1 && int_or_default(ci->params, ctx->id("WRITE_WIDTH_B"), 0) == 36) || (ci->type == id_RAMB36E1_RAMB36E1 && int_or_default(ci->params, ctx->id("WRITE_WIDTH_B"), 0) == 72)); ci->timing_index = ctx->get_cell_timing_idx( ctx->idf("%s_%s_%s", ci->type.c_str(ctx), write_sdp ? "WSDP" : "WTDP", read_sdp ? "RSDP" : "RTDP")); } } } void XilinxImpl::index_control_sets() { idict control_sets; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type == id_SLICE_FFX) { auto &ct = cell_tags.at(ci->flat_index); FFControlSet ctrl_set; ctrl_set.clk = ct.ff.clk ? ct.ff.clk->name : IdString(); ctrl_set.ce = ct.ff.ce ? ct.ff.ce->name : IdString(); ctrl_set.sr = ct.ff.sr ? ct.ff.sr->name : IdString(); ctrl_set.flags = (ct.ff.is_clkinv ? FFControlSet::IS_CLKINV : 0) | (ct.ff.is_srinv ? FFControlSet::IS_SRINV : 0) | (ct.ff.is_latch ? FFControlSet::IS_LATCH : 0) | (ct.ff.ffsync ? FFControlSet::FFSYNC : 0); ct.ff.control_set = control_sets(ctrl_set); } } log_info("Indexed %d control sets.\n", int(control_sets.size())); } bool XilinxImpl::is_general_routing(WireId wire) const { IdString intent = ctx->getWireType(wire); return !intent.in(id_INTENT_DEFAULT, id_NODE_DEDICATED, id_NODE_OPTDELAY, id_NODE_OUTPUT, id_NODE_INT_INTERFACE, id_PINFEED, id_INPUT, id_PADOUTPUT, id_PADINPUT, id_IOBINPUT, id_IOBOUTPUT, id_GENERIC, id_IOBIN2OUT, id_INTENT_SITE_WIRE, id_INTENT_SITE_GND); } void XilinxImpl::find_source_sink_locs() { for (auto &net : ctx->nets) { NetInfo *ni = net.second.get(); for (auto &usr : ni->users) { BelId bel = usr.cell->bel; if (bel == BelId() || is_logic_tile(bel)) continue; // don't need to do this for logic bels, which are always next to their INT WireId sink = ctx->getNetinfoSinkWire(ni, usr, 0); if (sink == WireId() || sink_locs.count(sink)) continue; std::queue visit; dict backtrace; int iter = 0; // as this is a best-effort optimisation to slightly improve routing, // don't spend too long with a nice low iteration limit const int iter_max = 500; visit.push(sink); while (!visit.empty() && iter < iter_max) { ++iter; WireId cursor = visit.front(); visit.pop(); if (is_general_routing(cursor)) { Loc loc(0, 0, 0); tile_xy(ctx->chip_info, cursor.tile, loc.x, loc.y); sink_locs[sink] = loc; while (backtrace.count(cursor)) { cursor = backtrace.at(cursor); if (!sink_locs.count(cursor)) { sink_locs[cursor] = loc; } } break; } for (auto pip : ctx->getPipsUphill(cursor)) { WireId src = ctx->getPipSrcWire(pip); if (!backtrace.count(src)) { backtrace[src] = cursor; visit.push(src); } } } } auto &drv = ni->driver; if (drv.cell != nullptr) { BelId bel = drv.cell->bel; if (bel == BelId() || is_logic_tile(bel)) continue; // don't need to do this for logic bels, which are always next to their INT WireId source = ctx->getNetinfoSourceWire(ni); if (source == WireId() || source_locs.count(source)) continue; std::queue visit; dict backtrace; int iter = 0; // as this is a best-effort optimisation to slightly improve routing, // don't spend too long with a nice low iteration limit const int iter_max = 500; visit.push(source); while (!visit.empty() && iter < iter_max) { ++iter; WireId cursor = visit.front(); visit.pop(); if (is_general_routing(cursor)) { Loc loc(0, 0, 0); tile_xy(ctx->chip_info, cursor.tile, loc.x, loc.y); source_locs[source] = loc; while (backtrace.count(cursor)) { cursor = backtrace.at(cursor); if (!source_locs.count(cursor)) { source_locs[cursor] = loc; } } break; } for (auto pip : ctx->getPipsDownhill(cursor)) { WireId dst = ctx->getPipDstWire(pip); if (!backtrace.count(dst)) { backtrace[dst] = cursor; visit.push(dst); } } } } } } delay_t XilinxImpl::estimateDelay(WireId src, WireId dst) const { int sx, sy, dx, dy; tile_xy(ctx->chip_info, src.tile, sx, sy); tile_xy(ctx->chip_info, dst.tile, dx, dy); auto fnd_src = source_locs.find(src); if (fnd_src != source_locs.end()) { sx = fnd_src->second.x; sy = fnd_src->second.y; } auto fnd_snk = sink_locs.find(dst); if (fnd_snk != sink_locs.end()) { dx = fnd_snk->second.x; dy = fnd_snk->second.y; } // TODO: improve sophistication here based on old nextpnr-xilinx code return 800 + 50 * (std::abs(dy - sy) + std::abs(dx - sx)); } delay_t XilinxImpl::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const { int sx, sy, dx, dy; tile_xy(ctx->chip_info, src_bel.tile, sx, sy); tile_xy(ctx->chip_info, dst_bel.tile, dx, dy); // TODO: improve sophistication here based on old nextpnr-xilinx code return 500 + 50 * (std::abs(dy - sy) + std::abs(dx - sx)); } BoundingBox XilinxImpl::getRouteBoundingBox(WireId src, WireId dst) const { int x0, y0, x1, y1; auto expand = [&](int x, int y) { x0 = std::min(x0, x); x1 = std::max(x1, x); y0 = std::min(y0, y); y1 = std::max(y1, y); }; tile_xy(ctx->chip_info, src.tile, x0, y0); x1 = x0; y1 = y0; int dx, dy; tile_xy(ctx->chip_info, dst.tile, dx, dy); expand(dx, dy); auto fnd_src = source_locs.find(src); if (fnd_src != source_locs.end()) { expand(fnd_src->second.x, fnd_src->second.y); } auto fnd_snk = sink_locs.find(dst); if (fnd_snk != sink_locs.end()) { expand(fnd_snk->second.x, fnd_snk->second.y); } return {x0 - 2, y0 - 2, x1 + 2, y1 + 2}; } namespace { struct XilinxArch : HimbaechelArch { XilinxArch() : HimbaechelArch("xilinx") {}; bool match_device(const std::string &device) override { return device.size() > 3 && device.substr(0, 3) == "xc7"; } std::unique_ptr create(const std::string &device) override { return std::make_unique(); } } xilinxArch; } // namespace NEXTPNR_NAMESPACE_END