/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2019-2023 gatecat * Copyright (C) 2023 Hans Baier * * 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 "extra_data.h" #include "himbaechel_api.h" #include "log.h" #include "nextpnr.h" #include "pins.h" #include "util.h" #include "xilinx.h" #define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" #include "himbaechel_constids.h" NEXTPNR_NAMESPACE_BEGIN namespace { struct FasmBackend { Context *ctx; XilinxImpl *uarch; std::ostream &out; std::vector fasm_ctx; dict> pips_by_tile; dict> invertible_pins; FasmBackend(Context *ctx, XilinxImpl *uarch, std::ostream &out) : ctx(ctx), uarch(uarch), out(out) {}; void push(const std::string &x) { fasm_ctx.push_back(x); } void pop() { fasm_ctx.pop_back(); } void pop(int N) { for (int i = 0; i < N; i++) fasm_ctx.pop_back(); } bool last_was_blank = true; void blank() { if (!last_was_blank) out << std::endl; last_was_blank = true; } void write_prefix() { for (auto &x : fasm_ctx) out << x << "."; last_was_blank = false; } void write_bit(const std::string &name, bool value = true) { if (value) { write_prefix(); out << name << std::endl; } } void write_vector(const std::string &name, const std::vector &value, bool invert = false) { write_prefix(); out << name << " = " << int(value.size()) << "'b"; for (auto bit : boost::adaptors::reverse(value)) out << ((bit ^ invert) ? '1' : '0'); out << std::endl; } void write_int_vector(const std::string &name, uint64_t value, int width, bool invert = false) { std::vector bits(width, false); for (int i = 0; i < width; i++) bits[i] = (value & (1ULL << i)) != 0; write_vector(name, bits, invert); } struct PseudoPipKey { IdString tileType; IdString dest; IdString source; bool operator==(const PseudoPipKey &b) const { return std::tie(this->tileType, this->dest, this->source) == std::tie(b.tileType, b.dest, b.source); } unsigned int hash() const { return mkhash(mkhash(tileType.hash(), source.hash()), dest.hash()); } }; dict> pp_config; void get_pseudo_pip_data() { /* * Create the mapping from pseudo pip tile type, dest wire, and source wire, to * the config bits set when that pseudo pip is used */ for (std::string s : {"L", "R"}) for (std::string s2 : {"", "_TBYTESRC", "_TBYTETERM", "_SING"}) for (std::string i : (s2 == "_SING") ? std::vector{"", "0", "1"} : std::vector{"0", "1"}) { pp_config[{ctx->id(s + "IOI3" + s2), ctx->id(s + "IOI_OLOGIC" + i + "_OQ"), ctx->id("IOI_OLOGIC" + i + "_D1")}] = {"OLOGIC_Y" + i + ".OMUX.D1", "OLOGIC_Y" + i + ".OQUSED", "OLOGIC_Y" + i + ".OSERDES.DATA_RATE_TQ.BUF"}; pp_config[{ctx->id(s + "IOI3" + s2), ctx->id("IOI_ILOGIC" + i + "_O"), ctx->id(s + "IOI_ILOGIC" + i + "_D")}] = {"IDELAY_Y" + i + ".IDELAY_TYPE_FIXED", "ILOGIC_Y" + i + ".ZINV_D"}; pp_config[{ctx->id(s + "IOI3" + s2), ctx->id("IOI_ILOGIC" + i + "_O"), ctx->id(s + "IOI_ILOGIC" + i + "_DDLY")}] = {"ILOGIC_Y" + i + ".IDELMUXE3.P0", "ILOGIC_Y" + i + ".ZINV_D"}; pp_config[{ctx->id(s + "IOI3" + s2), ctx->id(s + "IOI_OLOGIC" + i + "_TQ"), ctx->id("IOI_OLOGIC" + i + "_T1")}] = {"OLOGIC_Y" + i + ".ZINV_T1"}; if (i == "0") { pp_config[{ctx->id(s + "IOB33" + s2), id_IOB_O_IN1, id_IOB_O_OUT0}] = {}; pp_config[{ctx->id(s + "IOB33" + s2), id_IOB_O_OUT0, id_IOB_O0}] = {}; pp_config[{ctx->id(s + "IOB33" + s2), id_IOB_T_IN1, id_IOB_T_OUT0}] = {}; pp_config[{ctx->id(s + "IOB33" + s2), id_IOB_T_OUT0, id_IOB_T0}] = {}; pp_config[{ctx->id(s + "IOB33" + s2), id_IOB_DIFFI_IN0, id_IOB_PADOUT1}] = {}; } } for (std::string s2 : {"", "_TBYTESRC", "_TBYTETERM", "_SING"}) for (std::string i : (s2 == "_SING") ? std::vector{"0"} : std::vector{"0", "1"}) { pp_config[{ctx->id("RIOI" + s2), ctx->id("RIOI_OLOGIC" + i + "_OQ"), ctx->id("IOI_OLOGIC" + i + "_D1")}] = {"OLOGIC_Y" + i + ".OMUX.D1", "OLOGIC_Y" + i + ".OQUSED", "OLOGIC_Y" + i + ".OSERDES.DATA_RATE_TQ.BUF"}; pp_config[{ctx->id("RIOI" + s2), ctx->id("RIOI_OLOGIC" + i + "_OFB"), ctx->id("RIOI_OLOGIC" + i + "_OQ")}] = {}; pp_config[{ctx->id("RIOI" + s2), ctx->id("RIOI_O" + i), ctx->id("RIOI_ODELAY" + i + "_DATAOUT")}] = {}; pp_config[{ctx->id("RIOI" + s2), ctx->id("RIOI_OLOGIC" + i + "_OFB"), ctx->id("IOI_OLOGIC" + i + "_D1")}] = {"OLOGIC_Y" + i + ".OMUX.D1", "OLOGIC_Y" + i + ".OSERDES.DATA_RATE_TQ.BUF"}; pp_config[{ctx->id("RIOI" + s2), ctx->id("IOI_ILOGIC" + i + "_O"), ctx->id("RIOI_ILOGIC" + i + "_D")}] = {"ILOGIC_Y" + i + ".ZINV_D"}; pp_config[{ctx->id("RIOI" + s2), ctx->id("IOI_ILOGIC" + i + "_O"), ctx->id("RIOI_ILOGIC" + i + "_DDLY")}] = {"ILOGIC_Y" + i + ".IDELMUXE3.P0", "ILOGIC_Y" + i + ".ZINV_D"}; pp_config[{ctx->id("RIOI" + s2), ctx->id("RIOI_OLOGIC" + i + "_TQ"), ctx->id("IOI_OLOGIC" + i + "_T1")}] = {"OLOGIC_Y" + i + ".ZINV_T1"}; pp_config[{ctx->id("RIOI" + s2), ctx->id("RIOI_OLOGIC" + i + "_OFB"), ctx->id("RIOI_ODELAY" + i + "_ODATAIN")}] = {"OLOGIC_Y" + i + ".ZINV_ODATAIN"}; if (i == "0") { pp_config[{ctx->id("RIOB18" + s2), id_IOB_O_IN1, id_IOB_O_OUT0}] = {}; pp_config[{ctx->id("RIOB18" + s2), id_IOB_O_OUT0, id_IOB_O0}] = {}; pp_config[{ctx->id("RIOB18" + s2), id_IOB_T_IN1, id_IOB_T_OUT0}] = {}; pp_config[{ctx->id("RIOB18" + s2), id_IOB_T_OUT0, id_IOB_T0}] = {}; pp_config[{ctx->id("RIOB18" + s2), id_IOB_DIFFI_IN0, id_IOB_PADOUT1}] = {}; } } for (std::string s1 : {"TOP", "BOT"}) { for (std::string s2 : {"L", "R"}) { for (int i = 0; i < 12; i++) { std::string ii = std::to_string(i); std::string hck = s2 + ii; std::string buf = std::string((s2 == "R") ? "X1Y" : "X0Y") + ii; pp_config[{ctx->id("CLK_HROW_" + s1 + "_R"), ctx->id("CLK_HROW_CK_HCLK_OUT_" + hck), ctx->id("CLK_HROW_CK_MUX_OUT_" + hck)}] = {"BUFHCE.BUFHCE_" + buf + ".IN_USE", "BUFHCE.BUFHCE_" + buf + ".ZINV_CE"}; } } for (int i = 0; i < 16; i++) { std::string ii = std::to_string(i); pp_config[{ctx->id("CLK_BUFG_" + s1 + "_R"), ctx->id("CLK_BUFG_BUFGCTRL" + ii + "_O"), ctx->id("CLK_BUFG_BUFGCTRL" + ii + "_I0")}] = { "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".IN_USE", "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".IS_IGNORE1_INVERTED", "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".ZINV_CE0", "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".ZINV_S0"}; pp_config[{ctx->id("CLK_BUFG_" + s1 + "_R"), ctx->id("CLK_BUFG_BUFGCTRL" + ii + "_O"), ctx->id("CLK_BUFG_BUFGCTRL" + ii + "_I1")}] = { "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".IN_USE", "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".IS_IGNORE0_INVERTED", "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".ZINV_CE1", "BUFGCTRL.BUFGCTRL_X0Y" + ii + ".ZINV_S1"}; } } int rclk_y_to_i[4] = {2, 3, 0, 1}; for (int y = 0; y < 4; y++) { std::string yy = std::to_string(y); std::string ii = std::to_string(rclk_y_to_i[y]); pp_config[{id_HCLK_IOI3, ctx->id("HCLK_IOI_RCLK_OUT" + ii), ctx->id("HCLK_IOI_RCLK_BEFORE_DIV" + ii)}] = { "BUFR_Y" + yy + ".IN_USE", "BUFR_Y" + yy + ".BUFR_DIVIDE.BYPASS"}; pp_config[{id_HCLK_IOI, ctx->id("HCLK_IOI_RCLK_OUT" + ii), ctx->id("HCLK_IOI_RCLK_BEFORE_DIV" + ii)}] = { "BUFR_Y" + yy + ".IN_USE", "BUFR_Y" + yy + ".BUFR_DIVIDE.BYPASS"}; } // FIXME: shouldn't these be in the X-RAY ppips database? for (char c : {'L', 'R'}) { for (int i = 0; i < 24; i++) { pp_config[{ctx->idf("INT_INTERFACE_%c", c), ctx->idf("INT_INTERFACE_LOGIC_OUTS_%c%d", c, i), ctx->idf("INT_INTERFACE_LOGIC_OUTS_%c_B%d", c, i)}]; } } } void write_pip(PipId pip, NetInfo *net) { pips_by_tile[pip.tile].push_back(pip); auto dst_intent = ctx->getWireType(ctx->getPipDstWire(pip)); if (dst_intent == id_PSEUDO_GND || dst_intent == id_PSEUDO_VCC) return; auto &pd = chip_pip_info(ctx->chip_info, pip); const auto &extra_data = *reinterpret_cast(pd.extra_data.get()); unsigned pip_type = pd.flags; if (pip_type != PIP_TILE_ROUTING && pip_type != PIP_SITE_INTERNAL) return; IdString src = IdString(chip_tile_info(ctx->chip_info, pip.tile).wires[pd.src_wire].name); IdString dst = IdString(chip_tile_info(ctx->chip_info, pip.tile).wires[pd.dst_wire].name); // handle certain site internal pips: // this is necessary, because in tristate outputs, the // ZINV_T1 bit needs to be set, because in the OLOGIC tiles the // tristate control signals are inverted if this bit is not set // this only applies to router1, because router2 does not generate // site internal pips here. if (pip_type == PIP_SITE_INTERNAL) { if (src.str(ctx) == "T1" && dst.str(ctx) == "T1INV_OUT") { auto srcwire_uphill_iter = ctx->getPipsUphill(ctx->getPipSrcWire(pip)); auto uphill = srcwire_uphill_iter.begin(); if (uphill != srcwire_uphill_iter.end()) { // source wire should be like: LIOI3_X0Y73/IOI_OLOGIC1_T1 auto loc = ctx->getWireName(ctx->getPipSrcWire(*uphill)).str(ctx); boost::replace_all(loc, "/", "."); boost::erase_all(loc, "_T1"); boost::replace_all(loc, "IOI_OLOGIC", "OLOGIC_Y"); // the replacements transformed it into : LIOI3_X0Y73.OLOGIC_Y1 out << loc << "." << "ZINV_T1" << std::endl; } } return; } // handle tile routing pips IdString tile_type = IdString(chip_tile_info(ctx->chip_info, pip.tile).type_name); PseudoPipKey ppk{tile_type, dst, src}; if (pp_config.count(ppk)) { auto &pp = pp_config.at(ppk); std::string tile_name = uarch->tile_name(pip.tile); for (auto c : pp) { if (boost::starts_with(tile_name, "RIOI3_SING") || boost::starts_with(tile_name, "LIOI3_SING") || boost::starts_with(tile_name, "RIOI_SING")) { // Need to flip for top HCLK bool is_top_sing = pip.tile < uarch->hclk_for_ioi(pip.tile); if (is_top_sing) { auto y0pos = c.find("Y0"); if (y0pos != std::string::npos) c.replace(y0pos, 2, "Y1"); } } out << tile_name << "." << c << std::endl; } if (!pp.empty()) last_was_blank = false; } else { if (extra_data.pip_config == 1) log_warning("Unprocessed route-thru %s.%s.%s\n!", tile_type.c_str(ctx), src.c_str(ctx), dst.c_str(ctx)); std::string tile_name = uarch->tile_name(pip.tile); std::string dst_name = dst.str(ctx); std::string src_name = src.str(ctx); if (boost::starts_with(tile_name, "DSP_L") || boost::starts_with(tile_name, "DSP_R")) { // FIXME: PPIPs missing for DSPs return; } std::string orig_dst_name = dst_name; if (boost::starts_with(tile_name, "RIOI3_SING") || boost::starts_with(tile_name, "LIOI3_SING") || boost::starts_with(tile_name, "RIOI_SING")) { // FIXME: PPIPs missing for SING IOI3s if ((boost::contains(src_name, "IMUX") || boost::contains(src_name, "CTRL0")) && !boost::contains(dst_name, "CLK")) return; auto spos = src_name.find("_SING_"); if (spos != std::string::npos) src_name.erase(spos, 5); // Need to flip for top HCLK // TODO bool is_top_sing = pip.tile < uarch->hclk_for_ioi(pip.tile); if (is_top_sing) { auto us0pos = dst_name.find("_0"); if (us0pos != std::string::npos) dst_name.replace(us0pos, 2, "_1"); auto ol0pos = dst_name.find("OLOGIC0"); if (ol0pos != std::string::npos) { dst_name.replace(ol0pos, 7, "OLOGIC1"); us0pos = src_name.find("_0"); if (us0pos != std::string::npos) src_name.replace(us0pos, 2, "_1"); } } } if (boost::contains(tile_name, "IOI")) { if (boost::contains(dst_name, "OCLKB") && boost::contains(src_name, "IOI_OCLKM_")) return; // missing, not sure if really a ppip? } out << tile_name << "."; out << dst_name << "."; out << src_name << std::endl; if (boost::contains(tile_name, "IOI") && boost::starts_with(dst_name, "IOI_OCLK_")) { dst_name.insert(dst_name.find("OCLK") + 4, 1, 'M'); orig_dst_name.insert(dst_name.find("OCLK") + 4, 1, 'M'); WireId w = uarch->lookup_wire(pip.tile, ctx->id(orig_dst_name)); NPNR_ASSERT(w != WireId()); if (ctx->getBoundWireNet(w) == nullptr) { out << tile_name << "."; out << dst_name << "."; out << src_name << std::endl; } } last_was_blank = false; } }; // Get the set of input signals for a LUT-type cell std::vector get_inputs(CellInfo *cell) { IdString type = ctx->id(str_or_default(cell->attrs, id_X_ORIG_TYPE, "")); if (type == id_LUT1) return {id_I0}; else if (type == id_LUT2) return {id_I0, id_I1}; else if (type == id_LUT3) return {id_I0, id_I1, id_I2}; else if (type == id_LUT4) return {id_I0, id_I1, id_I2, id_I3}; else if (type == id_LUT5) return {id_I0, id_I1, id_I2, id_I3, id_I4}; else if (type == id_LUT6) return {id_I0, id_I1, id_I2, id_I3, id_I4, id_I5}; else if (type == id_RAMD64E) return {id_RADR0, id_RADR1, id_RADR2, id_RADR3, id_RADR4, id_RADR5}; else if (type == id_SRL16E) return {id_A0, id_A1, id_A2, id_A3}; else if (type == id_SRLC32E) return {ctx->id("A[0]"), ctx->id("A[1]"), ctx->id("A[2]"), ctx->id("A[3]"), ctx->id("A[4]")}; else if (type == id_RAMD32) return {id_RADR0, id_RADR1, id_RADR2, id_RADR3, id_RADR4}; else NPNR_ASSERT_FALSE("unsupported LUT-type cell"); } // Process LUT initialisation std::vector get_lut_init(CellInfo *lut6, CellInfo *lut5) { std::vector bits(64, false); std::vector phys_inputs; for (int i = 1; i <= 6; i++) phys_inputs.push_back(ctx->id("A" + std::to_string(i))); for (int i = 0; i < 2; i++) { CellInfo *lut = (i == 1) ? lut5 : lut6; if (lut == nullptr) continue; auto lut_inputs = get_inputs(lut); dict> phys_to_log; dict log_to_bit; for (int j = 0; j < int(lut_inputs.size()); j++) log_to_bit[lut_inputs[j].str(ctx)] = j; for (int j = 0; j < 6; j++) { // Get the LUT physical to logical mapping phys_to_log[j]; if (!lut->attrs.count(ctx->idf("X_ORIG_PORT_%s", phys_inputs[j].c_str(ctx)))) continue; std::string orig = lut->attrs.at(ctx->idf("X_ORIG_PORT_%s", phys_inputs[j].c_str(ctx))).as_string(); boost::split(phys_to_log[j], orig, boost::is_any_of(" ")); } int lbound = 0, ubound = 64; // Fracturable LUTs if (lut5 && lut6) { lbound = (i == 1) ? 0 : 32; ubound = (i == 1) ? 32 : 64; } Property init = get_or_default(lut->params, id_INIT, Property()).extract(0, 64); for (int j = lbound; j < ubound; j++) { int log_index = 0; for (int k = 0; k < 6; k++) { if ((j & (1 << k)) == 0) continue; for (auto &p2l : phys_to_log[k]) log_index |= (1 << log_to_bit[p2l]); } bits[j] = (init.str.at(log_index) == Property::S1); } } return bits; }; // Return the name for a half-logic-tile std::string get_half_name(int half, bool is_m) { if (is_m) return half ? "SLICEL_X1" : "SLICEM_X0"; else return half ? "SLICEL_X1" : "SLICEL_X0"; } std::string get_bel_name(BelId bel) { return uarch->bel_name_in_site(bel).str(ctx); } void write_routing_bel(WireId dst_wire) { for (auto pip : ctx->getPipsUphill(dst_wire)) { if (ctx->getBoundPipNet(pip) != nullptr) { auto &pd = chip_pip_info(ctx->chip_info, pip); const auto &extra_data = *reinterpret_cast(pd.extra_data.get()); std::string belname = IdString(extra_data.bel_name).str(ctx); std::string pinname = IdString(extra_data.pip_config).str(ctx); bool skip_pinname = false; // Ignore modes with no associated bit (X-ray omission??) if (belname == "WEMUX" && pinname == "WE") continue; if (belname.substr(1) == "DI1MUX") { belname = "DI1MUX"; } if (belname.substr(1) == "CY0") { if (pinname.substr(1) == "5") skip_pinname = true; else continue; } write_prefix(); out << belname; if (!skip_pinname) out << "." << pinname; out << std::endl; } } } // Process flipflops in a half-tile void write_ffs_config(int tile, int half) { bool found_ff = false; bool negedge_ff = false; bool is_latch = false; bool is_sync = false; bool is_clkinv = false; bool is_srused = false; bool is_ceused = false; #define SET_CHECK(dst, src) \ do { \ if (found_ff) \ NPNR_ASSERT(dst == (src)); \ else \ dst = (src); \ } while (0) std::string tname = uarch->tile_name(tile); const auto <s = uarch->tile_status.at(tile).lts; if (!lts) return; push(tname); push(get_half_name(half, boost::contains(tname, "CLBLM"))); for (int i = 0; i < 4; i++) { CellInfo *ff1 = lts->cells[(half << 6) | (i << 4) | BEL_FF]; CellInfo *ff2 = lts->cells[(half << 6) | (i << 4) | BEL_FF2]; for (int j = 0; j < 2; j++) { CellInfo *ff = (j == 1) ? ff2 : ff1; if (ff == nullptr) continue; push(get_bel_name(ff->bel)); bool zrst = false, zinit = false; zinit = (int_or_default(ff->params, id_INIT, 0) != 1); IdString srsig; std::string type = str_or_default(ff->attrs, id_X_ORIG_TYPE, ""); if (type == "FDRE") { zrst = true; SET_CHECK(negedge_ff, false); SET_CHECK(is_latch, false); SET_CHECK(is_sync, true); } else if (type == "FDRE_1") { zrst = true; SET_CHECK(negedge_ff, true); SET_CHECK(is_latch, false); SET_CHECK(is_sync, true); } else if (type == "FDSE") { zrst = false; SET_CHECK(negedge_ff, false); SET_CHECK(is_latch, false); SET_CHECK(is_sync, true); } else if (type == "FDSE_1") { zrst = false; SET_CHECK(negedge_ff, true); SET_CHECK(is_latch, false); SET_CHECK(is_sync, true); } else if (type == "FDCE") { zrst = true; SET_CHECK(negedge_ff, false); SET_CHECK(is_latch, false); SET_CHECK(is_sync, false); } else if (type == "FDCE_1") { zrst = true; SET_CHECK(negedge_ff, true); SET_CHECK(is_latch, false); SET_CHECK(is_sync, false); } else if (type == "FDPE") { zrst = false; SET_CHECK(negedge_ff, false); SET_CHECK(is_latch, false); SET_CHECK(is_sync, false); } else if (type == "FDPE_1") { zrst = false; SET_CHECK(negedge_ff, true); SET_CHECK(is_latch, false); SET_CHECK(is_sync, false); } else { log_error("unsupported FF type: '%s'\n", type.c_str()); } write_bit("ZINI", zinit); write_bit("ZRST", zrst); pop(); if (negedge_ff) SET_CHECK(is_clkinv, true); else SET_CHECK(is_clkinv, int_or_default(ff->params, id_IS_C_INVERTED) == 1); NetInfo *sr = ff->getPort(id_SR), *ce = ff->getPort(id_CE); SET_CHECK(is_srused, sr != nullptr && sr->name != ctx->id("$PACKER_GND_NET")); SET_CHECK(is_ceused, ce != nullptr && ce->name != ctx->id("$PACKER_VCC_NET")); // Input mux write_routing_bel(ctx->getBelPinWire(ff->bel, id_D)); found_ff = true; } } write_bit("LATCH", is_latch); write_bit("FFSYNC", is_sync); write_bit("CLKINV", is_clkinv); write_bit("NOCLKINV", !is_clkinv); write_bit("SRUSEDMUX", is_srused); write_bit("CEUSEDMUX", is_ceused); pop(2); } // Get a named wire in the same site as a bel WireId get_site_wire(BelId site_bel, std::string name) { IdStringList bel_name = ctx->getBelName(site_bel); NPNR_ASSERT(bel_name.size() == 2); IdString tile_name = bel_name[0]; const std::string &bel_name_str = bel_name[1].str(ctx); size_t sep_pos = bel_name_str.find('.'); NPNR_ASSERT(sep_pos != std::string::npos); std::string site_name = bel_name_str.substr(0, sep_pos); IdString wire_name = ctx->idf("%s.%s", site_name.c_str(), name.c_str()); WireId wire = ctx->getWireByName(IdStringList::concat(tile_name, wire_name)); NPNR_ASSERT(wire != WireId()); return wire; } // Process LUTs and associated functionality in a half void write_luts_config(int tile, int half) { bool wa7_used = false, wa8_used = false; std::string tname = uarch->tile_name(tile); bool is_mtile = boost::contains(tname, "CLBLM"); bool is_slicem = is_mtile && (half == 0); const auto <s = uarch->tile_status.at(tile).lts; if (!lts) return; push(tname); push(get_half_name(half, is_mtile)); BelId bel_in_half = ctx->getBelByLocation(Loc(tile % ctx->chip_info->width, tile / ctx->chip_info->width, half << 6)); for (int i = 0; i < 4; i++) { CellInfo *lut6 = lts->cells[(half << 6) | (i << 4) | BEL_6LUT]; CellInfo *lut5 = lts->cells[(half << 6) | (i << 4) | BEL_5LUT]; // Write LUT initialisation if (lut6 != nullptr || lut5 != nullptr) { std::string lutname = stringf("%cLUT", "ABCD"[i]); push(lutname); write_vector("INIT[63:0]", get_lut_init(lut6, lut5)); // Write LUT mode config bool is_small = false, is_ram = false, is_srl = false; for (int j = 0; j < 2; j++) { CellInfo *lut = (j == 1) ? lut5 : lut6; if (lut == nullptr) continue; std::string type = str_or_default(lut->attrs, id_X_ORIG_TYPE); if (type == "RAMD64E" || type == "RAMS64E") { is_ram = true; } else if (type == "RAMD32" || type == "RAMS32") { is_ram = true; is_small = true; } else if (type == "SRL16E") { is_srl = true; is_small = true; } else if (type == "SRLC32E") { is_srl = true; } wa7_used |= (lut->getPort(id_WA7) != nullptr); wa8_used |= (lut->getPort(id_WA8) != nullptr); } if (is_slicem && i != 3) { write_routing_bel(get_site_wire(bel_in_half, stringf("%cDI1MUX_OUT", "ABCD"[i]))); } write_bit("SMALL", is_small); write_bit("RAM", is_ram); write_bit("SRL", is_srl); pop(); } write_routing_bel(get_site_wire(bel_in_half, stringf("%cMUX", "ABCD"[i]))); } write_bit("WA7USED", wa7_used); write_bit("WA8USED", wa8_used); if (is_slicem) write_routing_bel(get_site_wire(bel_in_half, "WEMUX_OUT")); pop(2); } void write_carry_config(int tile, int half) { std::string tname = uarch->tile_name(tile); bool is_mtile = boost::contains(tname, "CLBLM"); const auto <s = uarch->tile_status.at(tile).lts; if (!lts) return; CellInfo *carry = lts->cells[half << 6 | BEL_CARRY4]; if (carry == nullptr) return; push(tname); push(get_half_name(half, is_mtile)); write_routing_bel(get_site_wire(carry->bel, "PRECYINIT_OUT")); if (carry->getPort(id_CIN) != nullptr) write_bit("PRECYINIT.CIN"); push("CARRY4"); for (char c : {'A', 'B', 'C', 'D'}) write_routing_bel(get_site_wire(carry->bel, stringf("%cCY0_OUT", c))); pop(3); } void write_logic() { std::set used_logic_tiles; for (auto &cell : ctx->cells) { if (uarch->is_logic_tile(cell.second->bel)) used_logic_tiles.insert(cell.second->bel.tile); } for (int tile : used_logic_tiles) { write_luts_config(tile, 0); write_luts_config(tile, 1); write_ffs_config(tile, 0); write_ffs_config(tile, 1); write_carry_config(tile, 0); write_carry_config(tile, 1); blank(); } } void write_routing() { get_pseudo_pip_data(); for (auto &net : ctx->nets) { NetInfo *ni = net.second.get(); out << stringf("# routing for net %s", ni->name.c_str(ctx)) << std::endl; for (auto &w : ni->wires) { if (w.second.pip != PipId()) write_pip(w.second.pip, ni); } blank(); } } struct BankIoConfig { bool stepdown = false; bool vref = false; bool tmds_33 = false; bool lvds_25 = false; bool only_diff = false; }; dict ioconfig_by_hclk; void write_io_config(CellInfo *pad) { NetInfo *pad_net = pad->getPort(id_PAD); NPNR_ASSERT(pad_net != nullptr); std::string iostandard = str_or_default(pad->attrs, id_IOSTANDARD, "LVCMOS33"); std::string pulltype = str_or_default(pad->attrs, id_PULLTYPE, "NONE"); std::string slew = str_or_default(pad->attrs, id_SLEW, "SLOW"); Loc ioLoc = uarch->rel_site_loc(uarch->get_bel_site(pad->bel)); bool is_output = false, is_input = false; if (pad_net->driver.cell != nullptr) is_output = true; for (auto &usr : pad_net->users) if (boost::contains(usr.cell->type.str(ctx), "INBUF")) is_input = true; std::string tile = uarch->tile_name(pad->bel.tile); push(tile); bool is_riob18 = boost::starts_with(tile, "RIOB18_"); bool is_sing = boost::contains(tile, "_SING_"); bool is_top_sing = pad->bel.tile < uarch->hclk_for_iob(pad->bel); bool is_stepdown = false; bool is_lvcmos = boost::starts_with(iostandard, "LVCMOS"); bool is_low_volt_lvcmos = iostandard == "LVCMOS12" || iostandard == "LVCMOS15" || iostandard == "LVCMOS18"; auto yLoc = is_sing ? (is_top_sing ? 1 : 0) : (1 - ioLoc.y); push("IOB_Y" + std::to_string(yLoc)); bool has_diff_prefix = boost::starts_with(iostandard, "DIFF_"); bool is_tmds33 = iostandard == "TMDS_33"; bool is_lvds25 = iostandard == "LVDS_25"; bool is_lvds = boost::starts_with(iostandard, "LVDS"); bool only_diff = is_tmds33 || is_lvds; bool is_diff = only_diff || has_diff_prefix; if (has_diff_prefix) iostandard.erase(0, 5); bool is_sstl = iostandard == "SSTL12" || iostandard == "SSTL135" || iostandard == "SSTL15"; int hclk = uarch->hclk_for_iob(pad->bel); if (only_diff) ioconfig_by_hclk[hclk].only_diff = true; if (is_tmds33) ioconfig_by_hclk[hclk].tmds_33 = true; if (is_lvds25) ioconfig_by_hclk[hclk].lvds_25 = true; if (is_output) { // DRIVE int default_drive = (is_riob18 && iostandard == "LVCMOS12") ? 8 : 12; int drive = int_or_default(pad->attrs, id_DRIVE, default_drive); if ((iostandard == "LVCMOS33" || iostandard == "LVTTL") && is_riob18) log_error("high performance banks (RIOB18) do not support IO standard %s\n", iostandard.c_str()); if (iostandard == "SSTL135") write_bit("SSTL135.DRIVE.I_FIXED"); else if (is_riob18) { if ((iostandard == "LVCMOS18" || iostandard == "LVCMOS15")) write_bit("LVCMOS15_LVCMOS18.DRIVE.I12_I16_I2_I4_I6_I8"); else if (iostandard == "LVCMOS12") write_bit("LVCMOS12.DRIVE.I2_I4_I6_I8"); else if (iostandard == "LVDS") write_bit("LVDS.DRIVE.I_FIXED"); else if (is_sstl) { write_bit(iostandard + ".DRIVE.I_FIXED"); } } else { // IOB33 if (iostandard == "TMDS_33" && yLoc == 0) { write_bit("TMDS_33.DRIVE.I_FIXED"); write_bit("TMDS_33.OUT"); } else if (iostandard == "LVDS_25" && yLoc == 0) { write_bit("LVDS_25.DRIVE.I_FIXED"); write_bit("LVDS_25.OUT"); } else if ((iostandard == "LVCMOS15" && drive == 16) || iostandard == "SSTL15") write_bit("LVCMOS15_SSTL15.DRIVE.I16_I_FIXED"); else if (iostandard == "LVCMOS18" && (drive == 12 || drive == 8)) write_bit("LVCMOS18.DRIVE.I12_I8"); else if ((iostandard == "LVCMOS33" && drive == 16) || (iostandard == "LVTTL" && drive == 16)) write_bit("LVCMOS33_LVTTL.DRIVE.I12_I16"); else if ((iostandard == "LVCMOS33" && (drive == 8 || drive == 12)) || (iostandard == "LVTTL" && (drive == 8 || drive == 12))) write_bit("LVCMOS33_LVTTL.DRIVE.I12_I8"); else if ((iostandard == "LVCMOS33" && drive == 4) || (iostandard == "LVTTL" && drive == 4)) write_bit("LVCMOS33_LVTTL.DRIVE.I4"); else if (drive == 8 && (iostandard == "LVCMOS12" || iostandard == "LVCMOS25")) write_bit("LVCMOS12_LVCMOS25.DRIVE.I8"); else if (drive == 4 && (iostandard == "LVCMOS15" || iostandard == "LVCMOS18" || iostandard == "LVCMOS25")) write_bit("LVCMOS15_LVCMOS18_LVCMOS25.DRIVE.I4"); else if (is_lvcmos || iostandard == "LVTTL") write_bit(iostandard + ".DRIVE.I" + std::to_string(drive)); } // SSTL output used if (is_riob18 && is_sstl) write_bit(iostandard + ".IN_USE"); // SLEW if (is_riob18 && slew == "SLOW") { if (iostandard == "SSTL135") write_bit("SSTL135.SLEW.SLOW"); else if (iostandard == "SSTL15") write_bit("SSTL15.SLEW.SLOW"); else write_bit("LVCMOS12_LVCMOS15_LVCMOS18.SLEW.SLOW"); } else if (slew == "SLOW") { if (iostandard != "LVDS_25" && iostandard != "TMDS_33") write_bit("LVCMOS12_LVCMOS15_LVCMOS18_LVCMOS25_LVCMOS33_LVTTL_SSTL135_SSTL15.SLEW.SLOW"); } else if (is_riob18) write_bit(iostandard + ".SLEW.FAST"); else if (iostandard == "SSTL135" || iostandard == "SSTL15") write_bit("SSTL135_SSTL15.SLEW.FAST"); else write_bit("LVCMOS12_LVCMOS15_LVCMOS18_LVCMOS25_LVCMOS33_LVTTL.SLEW.FAST"); } if (is_input) { if (!is_diff) { if (iostandard == "LVCMOS33" || iostandard == "LVTTL" || iostandard == "LVCMOS25") { if (!is_riob18) write_bit("LVCMOS25_LVCMOS33_LVTTL.IN"); else log_error("high performance banks (RIOB18) do not support IO standard %s\n", iostandard.c_str()); } if (is_sstl) { ioconfig_by_hclk[hclk].vref = true; if (!is_riob18) write_bit("SSTL135_SSTL15.IN"); if (is_riob18) { write_bit("SSTL12_SSTL135_SSTL15.IN"); } if (!is_riob18 && pad->attrs.count(id_IN_TERM)) write_bit("IN_TERM." + pad->attrs.at(id_IN_TERM).as_string()); } if (is_low_volt_lvcmos) { write_bit("LVCMOS12_LVCMOS15_LVCMOS18.IN"); } } else /* is_diff */ { if (is_riob18) { // vivado generates these bits only for Y0 of a diff pair if (yLoc == 0) { write_bit("LVDS_SSTL12_SSTL135_SSTL15.IN_DIFF"); if (iostandard == "LVDS") write_bit("LVDS.IN_USE"); } } else { if (iostandard == "TDMS_33") write_bit("TDMS_33.IN_DIFF"); else write_bit("LVDS_25_SSTL135_SSTL15.IN_DIFF"); } if (pad->attrs.count(id_IN_TERM)) write_bit("IN_TERM." + pad->attrs.at(id_IN_TERM).as_string()); } // IN_ONLY if (!is_output) { if (is_riob18) { // vivado also sets this bit for DIFF_SSTL if (is_diff && (yLoc == 0)) write_bit("LVDS.IN_ONLY"); else write_bit("LVCMOS12_LVCMOS15_LVCMOS18_SSTL12_SSTL135_SSTL15.IN_ONLY"); } else write_bit("LVCMOS12_LVCMOS15_LVCMOS18_LVCMOS25_LVCMOS33_LVDS_25_LVTTL_SSTL135_SSTL15_TMDS_33.IN_" "ONLY"); } } if (!is_riob18 && (is_low_volt_lvcmos || is_sstl)) { if (iostandard == "SSTL12") { log_error("SSTL12 is only available on high performance banks."); } write_bit("LVCMOS12_LVCMOS15_LVCMOS18_SSTL135_SSTL15.STEPDOWN"); ioconfig_by_hclk[hclk].stepdown = true; is_stepdown = true; } if (is_riob18 && (is_input || is_output) && (boost::contains(iostandard, "SSTL") || iostandard == "LVDS")) { if (((yLoc == 0) && (iostandard == "LVDS")) || boost::contains(iostandard, "SSTL")) { // TODO: I get bit conflicts with this, it seems to work anyway. Test more. // write_bit("LVDS.IN_USE"); } } if (is_input && is_output && !is_diff && yLoc == 1) { if (is_riob18 && boost::starts_with(iostandard, "SSTL")) write_bit("SSTL12_SSTL135_SSTL15.IN"); } write_bit("PULLTYPE." + pulltype); pop(); // IOB_YN BelId inv; auto pad_bel_site = uarch->get_bel_site(pad->bel); if (is_riob18) inv = uarch->get_site_bel(pad_bel_site, ctx->id("IOB18S.O_ININV")); else inv = uarch->get_site_bel(pad_bel_site, ctx->id("IOB33S.O_ININV")); if (inv != BelId() && ctx->getBoundBelCell(inv) != nullptr) write_bit("OUT_DIFF"); if (is_stepdown && !is_sing) write_bit("IOB_Y" + std::to_string(ioLoc.y) + ".LVCMOS12_LVCMOS15_LVCMOS18_SSTL135_SSTL15.STEPDOWN"); pop(); // tile } void write_iol_config(CellInfo *ci) { std::string tile = uarch->tile_name(ci->bel.tile); push(tile); bool is_sing = boost::contains(tile, "_SING_"); bool is_top_sing = ci->bel.tile < uarch->hclk_for_ioi(ci->bel.tile); auto site_key = uarch->get_bel_site(ci->bel); std::string site = uarch->get_site_name(site_key).str(ctx); std::string sitetype = site.substr(0, site.find('_')); Loc siteloc = uarch->rel_site_loc(site_key); push(stringf("%s_Y%d", sitetype.c_str(), is_sing ? (is_top_sing ? 1 : 0) : (1 - siteloc.y))); if (ci->type == id_ILOGICE3_IFF) { write_bit("IDDR.IN_USE"); write_bit("IDDR_OR_ISERDES.IN_USE"); write_bit("ISERDES.MODE.MASTER"); write_bit("ISERDES.NUM_CE.N1"); // Switch IDELMUXE3 to include the IDELAY element, if we have an IDELAYE2 driving D NetInfo *d = ci->getPort(id_D); if (d == nullptr || d->driver.cell == nullptr) log_error("%s '%s' has disconnected D input\n", ci->type.c_str(ctx), ctx->nameOf(ci)); CellInfo *drv = d->driver.cell; if (boost::contains(drv->type.str(ctx), "IDELAYE2")) write_bit("IDELMUXE3.P0"); else write_bit("IDELMUXE3.P1"); // clock edge std::string edge = str_or_default(ci->params, id_DDR_CLK_EDGE, "OPPOSITE_EDGE"); if (edge == "SAME_EDGE") write_bit("IFF.DDR_CLK_EDGE.SAME_EDGE"); else if (edge == "OPPOSITE_EDGE") write_bit("IFF.DDR_CLK_EDGE.OPPOSITE_EDGE"); else log_error("unsupported clock edge parameter for cell '%s' at %s: %s. Supported are: SAME_EDGE and " "OPPOSITE_EDGE", ci->name.c_str(ctx), site.c_str(), edge.c_str()); std::string srtype = str_or_default(ci->params, id_SRTYPE, "SYNC"); if (srtype == "SYNC") write_bit("IFF.SRTYPE.SYNC"); else write_bit("IFF.SRTYPE.ASYNC"); write_bit("IFF.ZINV_C", !bool_or_default(ci->params, id_IS_CLK_INVERTED, false)); write_bit("ZINV_D", !bool_or_default(ci->params, id_IS_D_INVERTED, false)); auto init = int_or_default(ci->params, id_INIT_Q1, 0); if (init == 0) write_bit("IFF.ZINIT_Q1"); init = int_or_default(ci->params, id_INIT_Q2, 0); if (init == 0) write_bit("IFF.ZINIT_Q2"); auto sr_name = str_or_default(ci->attrs, id_X_ORIG_PORT_SR, "R"); if (sr_name == "R") { write_bit("IFF.ZSRVAL_Q1"); write_bit("IFF.ZSRVAL_Q2"); } } else if (ci->type.in(id_OLOGICE2_OUTFF, id_OLOGICE3_OUTFF)) { std::string edge = str_or_default(ci->params, id_DDR_CLK_EDGE, "OPPOSITE_EDGE"); if (edge == "SAME_EDGE") write_bit("ODDR.DDR_CLK_EDGE.SAME_EDGE"); write_bit("ODDR_TDDR.IN_USE"); write_bit("OQUSED"); write_bit("OSERDES.DATA_RATE_OQ.DDR"); write_bit("OSERDES.DATA_RATE_TQ.BUF"); std::string srtype = str_or_default(ci->params, id_SRTYPE, "SYNC"); if (srtype == "SYNC") write_bit("OSERDES.SRTYPE.SYNC"); for (std::string d : {"D1", "D2"}) write_bit("IS_" + d + "_INVERTED", bool_or_default(ci->params, ctx->id("IS_" + d + "_INVERTED"), false)); auto init = int_or_default(ci->params, id_INIT, 1); if (init == 0) write_bit("ZINIT_OQ"); write_bit("ODDR.SRUSED", ci->getPort(id_SR) != nullptr); auto sr_name = str_or_default(ci->attrs, id_X_ORIG_PORT_SR, "R"); if (sr_name == "R") write_bit("ZSRVAL_OQ"); auto clk_inv = bool_or_default(ci->params, id_IS_CLK_INVERTED); if (!clk_inv) write_bit("ZINV_CLK"); } else if (ci->type == id_OSERDESE2_OSERDESE2) { write_bit("ODDR.DDR_CLK_EDGE.SAME_EDGE"); write_bit("ODDR.SRUSED"); write_bit("ODDR_TDDR.IN_USE"); write_bit("OQUSED", ci->getPort(id_OQ) != nullptr); write_bit("ZINV_CLK", !bool_or_default(ci->params, id_IS_CLK_INVERTED, false)); for (std::string t : {"T1", "T2", "T3", "T4"}) write_bit("ZINV_" + t, (ci->getPort(ctx->id(t)) != nullptr || t == "T1") && !bool_or_default(ci->params, ctx->id("IS_" + t + "_INVERTED"), false)); for (std::string d : {"D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8"}) write_bit("IS_" + d + "_INVERTED", bool_or_default(ci->params, ctx->id("IS_" + d + "_INVERTED"), false)); write_bit("ZINIT_OQ", !bool_or_default(ci->params, id_INIT_OQ, false)); write_bit("ZINIT_TQ", !bool_or_default(ci->params, id_INIT_TQ, false)); write_bit("ZSRVAL_OQ", !bool_or_default(ci->params, id_SRVAL_OQ, false)); write_bit("ZSRVAL_TQ", !bool_or_default(ci->params, id_SRVAL_TQ, false)); push("OSERDES"); write_bit("IN_USE"); std::string type = str_or_default(ci->params, id_DATA_RATE_OQ, "BUF"); write_bit(std::string("DATA_RATE_OQ.") + ((ci->getPort(id_OQ) != nullptr) ? type : "BUF")); write_bit(std::string("DATA_RATE_TQ.") + ((ci->getPort(id_TQ) != nullptr) ? str_or_default(ci->params, id_DATA_RATE_TQ, "BUF") : "BUF")); int width = int_or_default(ci->params, id_DATA_WIDTH, 8); #if 0 write_bit("DATA_WIDTH.W" + std::to_string(width)); if (type == "DDR" && (width == 6 || width == 8)) { write_bit("DATA_WIDTH.DDR.W6_8"); write_bit("DATA_WIDTH.SDR.W2_4_5_6"); } else if (type == "SDR" && (width == 2 || width == 4 || width == 5 || width == 6)) { write_bit("DATA_WIDTH.SDR.W2_4_5_6"); } #else if (type == "DDR") write_bit("DATA_WIDTH.DDR.W" + std::to_string(width)); else if (type == "SDR") write_bit("DATA_WIDTH.SDR.W" + std::to_string(width)); else write_bit("DATA_WIDTH.W" + std::to_string(width)); #endif write_bit("SRTYPE.SYNC"); write_bit("TSRTYPE.SYNC"); pop(); } else if (ci->type == id_ISERDESE2_ISERDESE2) { std::string data_rate = str_or_default(ci->params, id_DATA_RATE); write_bit("IDDR_OR_ISERDES.IN_USE"); if (data_rate == "DDR") write_bit("IDDR.IN_USE"); write_bit("IFF.DDR_CLK_EDGE.OPPOSITE_EDGE"); write_bit("IFF.SRTYPE.SYNC"); for (int i = 1; i <= 4; i++) { write_bit("IFF.ZINIT_Q" + std::to_string(i), !bool_or_default(ci->params, ctx->idf("INIT_Q%d", i), false)); write_bit("IFF.ZSRVAL_Q" + std::to_string(i), !bool_or_default(ci->params, ctx->idf("SRVAL_Q%d", i), false)); } write_bit("IFF.ZINV_C", !bool_or_default(ci->params, id_IS_CLK_INVERTED, false)); write_bit("IFF.ZINV_OCLK", !bool_or_default(ci->params, id_IS_OCLK_INVERTED, false)); std::string iobdelay = str_or_default(ci->params, id_IOBDELAY, "NONE"); write_bit("IFFDELMUXE3.P0", (iobdelay == "IFD")); write_bit("ZINV_D", !bool_or_default(ci->params, id_IS_D_INVERTED, false) && (iobdelay != "IFD")); push("ISERDES"); write_bit("IN_USE"); int width = int_or_default(ci->params, id_DATA_WIDTH, 8); std::string mode = str_or_default(ci->params, id_INTERFACE_TYPE, "NETWORKING"); std::string rate = str_or_default(ci->params, id_DATA_RATE, "DDR"); write_bit(mode + "." + rate + ".W" + std::to_string(width)); write_bit("MODE." + str_or_default(ci->params, id_SERDES_MODE, "MASTER")); write_bit("NUM_CE.N" + std::to_string(int_or_default(ci->params, id_NUM_CE, 1))); pop(); } else if (ci->type == id_IDELAYE2_IDELAYE2) { write_bit("IN_USE"); write_bit("CINVCTRL_SEL", str_or_default(ci->params, id_CINVCTRL_SEL, "FALSE") == "TRUE"); write_bit("PIPE_SEL", str_or_default(ci->params, id_PIPE_SEL, "FALSE") == "TRUE"); write_bit("HIGH_PERFORMANCE_MODE", str_or_default(ci->params, id_HIGH_PERFORMANCE_MODE, "FALSE") == "TRUE"); write_bit("DELAY_SRC_" + str_or_default(ci->params, id_DELAY_SRC, "IDATAIN")); write_bit("IDELAY_TYPE_" + str_or_default(ci->params, id_IDELAY_TYPE, "FIXED")); write_int_vector("IDELAY_VALUE[4:0]", int_or_default(ci->params, id_IDELAY_VALUE, 0), 5, false); write_int_vector("ZIDELAY_VALUE[4:0]", int_or_default(ci->params, id_IDELAY_VALUE, 0), 5, true); write_bit("IS_DATAIN_INVERTED", bool_or_default(ci->params, id_IS_DATAIN_INVERTED, false)); write_bit("IS_IDATAIN_INVERTED", bool_or_default(ci->params, id_IS_IDATAIN_INVERTED, false)); } else if (ci->type == id_ODELAYE2_ODELAYE2) { write_bit("IN_USE"); write_bit("CINVCTRL_SEL", str_or_default(ci->params, id_CINVCTRL_SEL, "FALSE") == "TRUE"); write_bit("HIGH_PERFORMANCE_MODE", str_or_default(ci->params, id_HIGH_PERFORMANCE_MODE, "FALSE") == "TRUE"); auto type = str_or_default(ci->params, id_ODELAY_TYPE, "FIXED"); if (type != "FIXED") write_bit("ODELAY_TYPE_" + type); write_int_vector("ODELAY_VALUE[4:0]", int_or_default(ci->params, id_ODELAY_VALUE, 0), 5, false); write_int_vector("ZODELAY_VALUE[4:0]", int_or_default(ci->params, id_ODELAY_VALUE, 0), 5, true); write_bit("ZINV_ODATAIN", !bool_or_default(ci->params, id_IS_ODATAIN_INVERTED, false)); } else { NPNR_ASSERT_FALSE("unsupported IOLOGIC"); } pop(2); } void write_io() { for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type == id_PAD) { write_io_config(ci); blank(); } else if (ci->type.in(id_ILOGICE3_IFF, id_OLOGICE2_OUTFF, id_OLOGICE3_OUTFF, id_OSERDESE2_OSERDESE2, id_ISERDESE2_ISERDESE2, id_IDELAYE2_IDELAYE2, id_ODELAYE2_ODELAYE2)) { write_iol_config(ci); blank(); } } for (auto &hclk : ioconfig_by_hclk) { push(uarch->tile_name(hclk.first)); write_bit("STEPDOWN", hclk.second.stepdown); write_bit("VREF.V_675_MV", hclk.second.vref); write_bit("ONLY_DIFF_IN_USE", hclk.second.only_diff); write_bit("TMDS_33_IN_USE", hclk.second.tmds_33); write_bit("LVDS_25_IN_USE", hclk.second.lvds_25); pop(); } } std::vector used_wires_starting_with(int tile, const std::string &prefix, bool is_source) { std::vector wires; if (!pips_by_tile.count(tile)) return wires; for (auto pip : pips_by_tile[tile]) { auto &pd = chip_pip_info(ctx->chip_info, pip); int wire_index = is_source ? pd.src_wire : pd.dst_wire; std::string wire = IdString(chip_wire_info(ctx->chip_info, WireId(pip.tile, wire_index)).name).str(ctx); if (boost::starts_with(wire, prefix)) wires.push_back(wire); } return wires; } void write_clocking() { std::string name, type; std::set all_gclk; dict> hclk_by_row; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type == id_BUFGCTRL) { push(uarch->tile_name(ci->bel.tile)); auto xy = uarch->rel_site_loc(uarch->get_bel_site(ci->bel)); push(stringf("BUFGCTRL.BUFGCTRL_X%dY%d", xy.x, xy.y)); write_bit("IN_USE"); write_bit("INIT_OUT", bool_or_default(ci->params, id_INIT_OUT)); write_bit("IS_IGNORE0_INVERTED", bool_or_default(ci->params, id_IS_IGNORE0_INVERTED)); write_bit("IS_IGNORE1_INVERTED", bool_or_default(ci->params, id_IS_IGNORE1_INVERTED)); write_bit("ZINV_CE0", !bool_or_default(ci->params, id_IS_CE0_INVERTED)); write_bit("ZINV_CE1", !bool_or_default(ci->params, id_IS_CE1_INVERTED)); write_bit("ZINV_S0", !bool_or_default(ci->params, id_IS_S0_INVERTED)); write_bit("ZINV_S1", !bool_or_default(ci->params, id_IS_S1_INVERTED)); pop(2); } else if (ci->type == id_PLLE2_ADV_PLLE2_ADV) { write_pll(ci); } blank(); } for (int tile = 0; tile < ctx->chip_info->tile_insts.ssize(); tile++) { std::string name = uarch->tile_name(tile); std::string type = ctx->get_tile_type(tile).str(ctx); push(name); if (type == "HCLK_L" || type == "HCLK_R" || type == "HCLK_L_BOT_UTURN" || type == "HCLK_R_BOT_UTURN") { auto used_sources = used_wires_starting_with(tile, "HCLK_CK_", true); push("ENABLE_BUFFER"); for (auto s : used_sources) { if (boost::contains(s, "BUFHCLK")) { write_bit(s); hclk_by_row[tile / ctx->chip_info->width].insert(s.substr(s.find("BUFHCLK"))); } } pop(); } else if (boost::starts_with(type, "CLK_HROW")) { auto used_gclk = used_wires_starting_with(tile, "CLK_HROW_R_CK_GCLK", true); auto used_ck_in = used_wires_starting_with(tile, "CLK_HROW_CK_IN", true); for (auto s : used_gclk) { write_bit(s + "_ACTIVE"); all_gclk.insert(s.substr(s.find("GCLK"))); } for (auto s : used_ck_in) { if (boost::contains(s, "HROW_CK_INT")) continue; write_bit(s + "_ACTIVE"); } } else if (boost::starts_with(type, "HCLK_CMT")) { auto used_ccio = used_wires_starting_with(tile, "HCLK_CMT_CCIO", true); for (auto s : used_ccio) { write_bit(s + "_ACTIVE"); write_bit(s + "_USED"); } auto used_hclk = used_wires_starting_with(tile, "HCLK_CMT_CK_", true); for (auto s : used_hclk) { if (boost::contains(s, "BUFHCLK")) { write_bit(s + "_USED"); hclk_by_row[tile / ctx->chip_info->width].insert(s.substr(s.find("BUFHCLK"))); } } } pop(); blank(); } for (int tile = 0; tile < ctx->chip_info->tile_insts.ssize(); tile++) { std::string name = uarch->tile_name(tile); std::string type = ctx->get_tile_type(tile).str(ctx); push(name); if (type == "CLK_BUFG_REBUF") { for (auto &gclk : all_gclk) { write_bit(gclk + "_ENABLE_ABOVE"); write_bit(gclk + "_ENABLE_BELOW"); } } else if (boost::starts_with(type, "HCLK_CMT")) { for (auto &hclk : hclk_by_row[tile / ctx->chip_info->width]) { write_bit("HCLK_CMT_CK_" + hclk + "_USED"); } } pop(); blank(); } } void write_bram_width(CellInfo *ci, const std::string &name, bool is_36, bool is_y1) { int width = int_or_default(ci->params, ctx->id(name), 0); if (width == 0) return; int actual_width = width; if (is_36) { if (width == 1) actual_width = 1; else actual_width = width / 2; } if (((is_36 && width == 72) || (is_y1 && actual_width == 36)) && name == "READ_WIDTH_A") { write_bit(name + "_18"); } if (actual_width == 36) { write_bit("SDP_" + name.substr(0, name.length() - 2) + "_36"); if (name.find("WRITE") == 0) { write_bit(name.substr(0, name.size() - 1) + "A_18"); write_bit(name.substr(0, name.size() - 1) + "B_18"); } else if (name.find("READ") == 0) { write_bit(name.substr(0, name.size() - 1) + "B_18"); } } else { write_bit(name + "_" + std::to_string(actual_width)); } } void write_bram_init(int half, CellInfo *ci, bool is_36) { for (std::string mode : {"", "P"}) { for (int i = 0; i < (mode == "P" ? 8 : 64); i++) { bool has_init = false; std::vector init_data(256, false); if (is_36) { for (int j = 0; j < 2; j++) { IdString param = ctx->idf("INIT%s_%02X", mode.c_str(), i * 2 + j); if (ci->params.count(param)) { auto &init0 = ci->params.at(param); has_init = true; for (int k = half; k < 256; k += 2) { if (k >= int(init0.str.size())) break; init_data[j * 128 + (k / 2)] = init0.str[k] == Property::S1; } } } } else { IdString param = ctx->idf("INIT%s_%02X", mode.c_str(), i); if (ci->params.count(param)) { auto &init = ci->params.at(param); has_init = true; for (int k = 0; k < 256; k++) { if (k >= int(init.str.size())) break; init_data[k] = init.str[k] == Property::S1; } } } if (has_init) write_vector(stringf("INIT%s_%02X[255:0]", mode.c_str(), i), init_data); } } } void write_bram_half(int tile, int half, CellInfo *ci) { push(uarch->tile_name(tile)); push("RAMB18_Y" + std::to_string(half)); if (ci != nullptr) { bool is_36 = ci->type == id_RAMB36E1_RAMB36E1; write_bit("IN_USE"); write_bram_width(ci, "READ_WIDTH_A", is_36, half == 1); write_bram_width(ci, "READ_WIDTH_B", is_36, half == 1); write_bram_width(ci, "WRITE_WIDTH_A", is_36, half == 1); write_bram_width(ci, "WRITE_WIDTH_B", is_36, half == 1); write_bit("DOA_REG", bool_or_default(ci->params, id_DOA_REG, false)); write_bit("DOB_REG", bool_or_default(ci->params, id_DOB_REG, false)); for (auto &invpin : invertible_pins[ctx->id(ci->attrs[id_X_ORIG_TYPE].as_string())]) write_bit("ZINV_" + invpin.str(ctx), !bool_or_default(ci->params, ctx->id("IS_" + invpin.str(ctx) + "_INVERTED"), false)); for (auto wrmode : {"WRITE_MODE_A", "WRITE_MODE_B"}) { std::string mode = str_or_default(ci->params, ctx->id(wrmode), "WRITE_FIRST"); if (mode != "WRITE_FIRST") write_bit(std::string(wrmode) + "_" + mode); } write_vector("ZINIT_A[17:0]", std::vector(18, true)); write_vector("ZINIT_B[17:0]", std::vector(18, true)); write_vector("ZSRVAL_A[17:0]", std::vector(18, true)); write_vector("ZSRVAL_B[17:0]", std::vector(18, true)); write_bram_init(half, ci, is_36); } pop(); if (half == 0) { auto used_rdaddrcasc = used_wires_starting_with(tile, "BRAM_CASCOUT_ADDRARDADDR", false); auto used_wraddrcasc = used_wires_starting_with(tile, "BRAM_CASCOUT_ADDRBWRADDR", false); write_bit("CASCOUT_ARD_ACTIVE", !used_rdaddrcasc.empty()); write_bit("CASCOUT_BWR_ACTIVE", !used_wraddrcasc.empty()); } pop(); } void write_bram() { for (int tile = 0; tile < ctx->chip_info->tile_insts.ssize(); tile++) { IdString type = ctx->get_tile_type(tile); if (type.in(id_BRAM_L, id_BRAM_R)) { CellInfo *l = nullptr, *u = nullptr; const auto &bts = uarch->tile_status[tile].bts; if (bts) { if (bts->cells[BEL_RAM36] != nullptr) { l = bts->cells[BEL_RAM36]; u = bts->cells[BEL_RAM36]; } else { l = bts->cells[BEL_RAM18_L]; u = bts->cells[BEL_RAM18_U]; } } write_bram_half(tile, 0, l); write_bram_half(tile, 1, u); blank(); } } } double float_or_default(CellInfo *ci, const std::string &name, double def) { IdString p = ctx->id(name); if (!ci->params.count(p)) return def; auto &prop = ci->params.at(p); if (prop.is_string) return std::stod(prop.as_string()); else return prop.as_int64(); } void write_pll_clkout(const std::string &name, CellInfo *ci) { // FIXME: variable duty cycle int high = 1, low = 1, phasemux = 0, delaytime = 0, frac = 0; bool no_count = false, edge = false; double divide = float_or_default(ci, name + ((name == "CLKFBOUT") ? "_MULT" : "_DIVIDE"), 1); double phase = float_or_default(ci, name + "_PHASE", 1); if (divide <= 1) { no_count = true; } else { high = floor(divide / 2); low = int(floor(divide) - high); if (high != low) edge = true; if (name == "CLKOUT1" || name == "CLKFBOUT") frac = floor(divide * 8) - floor(divide) * 8; int phase_eights = floor((phase / 360) * divide * 8); phasemux = phase_eights % 8; delaytime = phase_eights / 8; } bool used = false; if (name == "DIVCLK" || name == "CLKFBOUT") { used = true; } else { used = ci->getPort(ctx->id(name)) != nullptr; } if (name == "DIVCLK") { write_int_vector("DIVCLK_DIVCLK_HIGH_TIME[5:0]", high, 6); write_int_vector("DIVCLK_DIVCLK_LOW_TIME[5:0]", low, 6); write_bit("DIVCLK_DIVCLK_EDGE[0]", edge); write_bit("DIVCLK_DIVCLK_NO_COUNT[0]", no_count); } else if (used) { write_bit(name + "_CLKOUT1_OUTPUT_ENABLE[0]"); write_int_vector(name + "_CLKOUT1_HIGH_TIME[5:0]", high, 6); write_int_vector(name + "_CLKOUT1_LOW_TIME[5:0]", low, 6); write_int_vector(name + "_CLKOUT1_PHASE_MUX[2:0]", phasemux, 3); write_bit(name + "_CLKOUT2_EDGE[0]", edge); write_bit(name + "_CLKOUT2_NO_COUNT[0]", no_count); write_int_vector(name + "_CLKOUT2_DELAY_TIME[5:0]", delaytime, 6); if (frac != 0) { write_bit(name + "_CLKOUT2_FRAC_EN[0]", edge); write_int_vector(name + "_CLKOUT2_FRAC[2:0]", frac, 3); } } } void write_pll(CellInfo *ci) { push(uarch->tile_name(ci->bel.tile)); push("PLLE2_ADV"); write_bit("IN_USE"); // FIXME: should be INV not ZINV (XRay error?) write_bit("ZINV_PWRDWN", bool_or_default(ci->params, id_IS_PWRDWN_INVERTED, false)); write_bit("ZINV_RST", bool_or_default(ci->params, id_IS_RST_INVERTED, false)); write_bit("INV_CLKINSEL", bool_or_default(ci->params, id_IS_CLKINSEL_INVERTED, false)); write_pll_clkout("DIVCLK", ci); write_pll_clkout("CLKFBOUT", ci); write_pll_clkout("CLKOUT0", ci); write_pll_clkout("CLKOUT1", ci); write_pll_clkout("CLKOUT2", ci); write_pll_clkout("CLKOUT3", ci); write_pll_clkout("CLKOUT4", ci); write_pll_clkout("CLKOUT5", ci); std::string comp = str_or_default(ci->params, id_COMPENSATION, "INTERNAL"); push("COMPENSATION"); if (comp == "INTERNAL") { // write_bit("INTERNAL"); write_bit("Z_ZHOLD_OR_CLKIN_BUF"); } else { NPNR_ASSERT_FALSE("unsupported compensation type"); } pop(); // FIXME: should these be calculated somehow? write_int_vector("FILTREG1_RESERVED[11:0]", 0x8, 12); write_int_vector("LKTABLE[39:0]", 0xB5BE8FA401ULL, 40); write_bit("LOCKREG3_RESERVED[0]"); write_int_vector("TABLE[9:0]", 0x3B4, 10); pop(2); } void write_dsp_cell(CellInfo *ci) { auto tile_name = uarch->tile_name(ci->bel.tile); auto tile_side = tile_name.at(4); push(tile_name); push("DSP48"); auto xy = uarch->rel_site_loc(uarch->get_bel_site(ci->bel)); auto dsp = stringf("DSP_%d", xy.y); push(dsp); auto write_bus_zinv = [&](std::string name, int width) { for (int i = 0; i < width; i++) { std::string b = stringf("[%d]", i); bool inv = (int_or_default(ci->params, ctx->id("IS_" + name + "_INVERTED"), 0) >> i) & 0x1; inv |= bool_or_default(ci->params, ctx->id("IS_" + name + b + "_INVERTED"), false); write_bit("ZIS_" + name + "_INVERTED" + b, !inv); } }; // value 1 is equivalent to 2, according to UG479 // but in real life, Vivado sets AREG_0 is 0, // no bit is 1, and AREG_2 is 2 auto areg = int_or_default(ci->params, ctx->id("AREG"), 1); if (areg == 0 or areg == 2) write_bit("AREG_" + std::to_string(areg)); auto ainput = str_or_default(ci->params, ctx->id("A_INPUT"), "DIRECT"); if (ainput == "CASCADE") write_bit("A_INPUT[0]"); // value 1 is equivalent to 2, according to UG479 // but in real life, Vivado sets AREG_0 is 0, // no bit is 1, and AREG_2 is 2 auto breg = int_or_default(ci->params, ctx->id("BREG"), 1); if (breg == 0 or breg == 2) write_bit("BREG_" + std::to_string(breg)); auto binput = str_or_default(ci->params, ctx->id("B_INPUT"), "DIRECT"); if (binput == "CASCADE") write_bit("B_INPUT[0]"); // Tolerate both int and string types for interoperability purposes auto use_dport = boolstr_or_default(ci->params, ctx->id("USE_DPORT"), false); if (use_dport == true) write_bit("USE_DPORT[0]"); auto use_simd = str_or_default(ci->params, ctx->id("USE_SIMD"), "ONE48"); if (use_simd == "TWO24") write_bit("USE_SIMD_FOUR12_TWO24"); if (use_simd == "FOUR12") write_bit("USE_SIMD_FOUR12"); // PATTERN const size_t pattern_size = 48; std::vector pattern_vector(pattern_size, false); bool pattern_found = boolvec_populate(ci->params, ctx->id("PATTERN"), pattern_vector); if (pattern_found) { write_vector("PATTERN[47:0]", pattern_vector); } auto autoreset_patdet = str_or_default(ci->params, ctx->id("AUTORESET_PATDET"), "NO_RESET"); if (autoreset_patdet == "RESET_MATCH") write_bit("AUTORESET_PATDET_RESET"); if (autoreset_patdet == "RESET_NOT_MATCH") write_bit("AUTORESET_PATDET_RESET_NOT_MATCH"); // MASK // Yosys gives us 48 bit, but prjxray only recognizes 46 bits // The most significant two bits seem to be zero, so let us just truncate them const size_t mask_size = 48; std::vector mask_vector(mask_size, true); bool mask_found = boolvec_populate(ci->params, ctx->id("MASK"), mask_vector); if (mask_found) { mask_vector.resize(46); write_vector("MASK[45:0]", mask_vector); } auto sel_mask = str_or_default(ci->params, ctx->id("SEL_MASK"), "MASK"); if (sel_mask == "C") write_bit("SEL_MASK_C"); if (sel_mask == "ROUNDING_MODE1") write_bit("SEL_MASK_ROUNDING_MODE1"); if (sel_mask == "ROUNDING_MODE2") write_bit("SEL_MASK_ROUNDING_MODE2"); write_bit("ZADREG[0]", !bool_or_default(ci->params, ctx->id("ADREG"), true)); write_bit("ZALUMODEREG[0]", !bool_or_default(ci->params, ctx->id("ALUMODEREG"))); write_bit("ZAREG_2_ACASCREG_1", !bool_or_default(ci->params, ctx->id("ACASCREG"))); write_bit("ZBREG_2_BCASCREG_1", !bool_or_default(ci->params, ctx->id("BCASCREG"))); write_bit("ZCARRYINREG[0]", !bool_or_default(ci->params, ctx->id("CARRYINREG"))); write_bit("ZCARRYINSELREG[0]", !bool_or_default(ci->params, ctx->id("CARRYINSELREG"))); write_bit("ZCREG[0]", !bool_or_default(ci->params, ctx->id("CREG"), true)); write_bit("ZDREG[0]", !bool_or_default(ci->params, ctx->id("DREG"), true)); write_bit("ZINMODEREG[0]", !bool_or_default(ci->params, ctx->id("INMODEREG"))); write_bus_zinv("ALUMODE", 4); write_bus_zinv("INMODE", 5); write_bus_zinv("OPMODE", 7); write_bit("ZMREG[0]", !bool_or_default(ci->params, ctx->id("MREG"))); write_bit("ZOPMODEREG[0]", !bool_or_default(ci->params, ctx->id("OPMODEREG"))); write_bit("ZPREG[0]", !bool_or_default(ci->params, ctx->id("PREG"))); write_bit("USE_DPORT[0]", boolstr_or_default(ci->params, ctx->id("USE_DPORT"), false)); write_bit("ZIS_CLK_INVERTED", !bool_or_default(ci->params, ctx->id("IS_CLK_INVERTED"))); write_bit("ZIS_CARRYIN_INVERTED", !bool_or_default(ci->params, ctx->id("IS_CARRYIN_INVERTED"))); pop(2); auto write_const_pins = [&](std::string const_net_name) { std::vector pins; const auto attr_name = "DSP_" + const_net_name + "_PINS"; const auto attr_value = str_or_default(ci->attrs, ctx->id(attr_name), ""); boost::split(pins, attr_value, boost::is_any_of(" ")); for (auto pin : pins) { if (boost::empty(pin)) continue; auto pin_basename = pin; boost::erase_all(pin_basename, "0123456789"); auto inv = bool_or_default(ci->params, ctx->id("IS_" + pin_basename + "_INVERTED"), 0); auto net_name = inv ? (const_net_name == "GND" ? "VCC" : "GND") : const_net_name; write_bit(stringf("%s_%s.DSP_%s_%c", dsp.c_str(), pin.c_str(), net_name.c_str(), tile_side)); } }; write_const_pins("GND"); write_const_pins("VCC"); pop(); } void write_ip() { for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type == id_DSP48E1_DSP48E1) { write_dsp_cell(ci); blank(); } } } void write_fasm() { get_invertible_pins(ctx, invertible_pins); write_logic(); write_io(); write_routing(); write_bram(); write_clocking(); write_ip(); } }; } // namespace void XilinxImpl::write_fasm(const std::string &filename) { std::ofstream out(filename); if (!out) log_error("failed to open file %s for writing (%s)\n", filename.c_str(), strerror(errno)); FasmBackend be(this->ctx, this, out); be.write_fasm(); } NEXTPNR_NAMESPACE_END