/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 David 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 "nextpnr.h" #include "design_utils.h" #include "cells.h" #include "log.h" #include "util.h" NEXTPNR_NAMESPACE_BEGIN static bool is_nextpnr_iob(Context *ctx, CellInfo *cell) { return cell->type == ctx->id("$nextpnr_ibuf") || cell->type == ctx->id("$nextpnr_obuf") || cell->type == ctx->id("$nextpnr_iobuf"); } static bool is_iob(Context *ctx, CellInfo *cell) { return cell->type == ctx->id("IOB"); } class LeuctraPacker { public: LeuctraPacker(Context *ctx) : ctx(ctx){}; private: // Process the contents of packed_cells and new_cells void flush_cells() { for (auto pcell : packed_cells) { ctx->cells.erase(pcell); } for (auto &ncell : new_cells) { ctx->cells[ncell->name] = std::move(ncell); } packed_cells.clear(); new_cells.clear(); } CellInfo *fetch_nxio(CellInfo *cell, IdString port) { PortInfo pi = cell->ports.at(port); if (pi.net == nullptr) return nullptr; CellInfo *res = nullptr; IdString res_port; if (is_nextpnr_iob(ctx, pi.net->driver.cell)) { res = pi.net->driver.cell; res_port = pi.net->driver.port; } else if (pi.net->driver.cell) { if (pi.net->driver.cell != cell || pi.net->driver.port != port) { log_error("Stray driver on net %s: %s %s\n", pi.net->name.c_str(ctx), pi.net->driver.cell->name.c_str(ctx), pi.net->driver.port.c_str(ctx)); } } for (auto &usr : pi.net->users) if (usr.cell == cell && usr.port == port) { continue; } else if (is_nextpnr_iob(ctx, usr.cell)) { if (res) { log_error("Two nextpnr bufs on net %s: %s %s\n", pi.net->name.c_str(ctx), usr.cell->name.c_str(ctx), res->name.c_str(ctx)); } res = usr.cell; res_port = usr.port; } else { log_error("Stray load on net %s: %s %s\n", pi.net->name.c_str(ctx), usr.cell->name.c_str(ctx), usr.port.c_str(ctx)); } if (!res) return nullptr; // Kill the connection. disconnect_port(ctx, res, res_port); disconnect_port(ctx, cell, port); return res; } // Insert IOB cells for ports that don't have one yet. void insert_iob() { log_info("Inserting IOBs...\n"); /* TODO: get this working maybe? */ for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (is_nextpnr_iob(ctx, ci)) { abort(); NPNR_ASSERT_FALSE("SURVIVED"); CellInfo *iob = nullptr; std::unique_ptr io_cell = create_leuctra_cell(ctx, ctx->id("IOB"), ci->name.str(ctx) + "$iob"); nxio_to_iob(ctx, ci, io_cell.get(), new_cells, packed_cells); new_cells.push_back(std::move(io_cell)); iob = new_cells.back().get(); packed_cells.insert(ci->name); if (iob != nullptr) { for (const auto &attr : ci->attrs) iob->attrs[attr.first] = attr.second; for (const auto ¶m : ci->params) iob->params[param.first] = param.second; auto loc_attr = iob->attrs.find(ctx->id("LOC")); if (loc_attr != iob->attrs.end()) { std::string pin = loc_attr->second.as_string(); BelId pinBel = ctx->getPackagePinBel(pin); if (pinBel == BelId()) { log_error("IO pin '%s' constrained to pin '%s', which does not exist for package '%s'.\n", iob->name.c_str(ctx), pin.c_str(), ctx->args.package.c_str()); } else { log_info("pin '%s' constrained to Bel '%s'.\n", iob->name.c_str(ctx), ctx->getBelName(pinBel).c_str(ctx)); } iob->attrs[ctx->id("BEL")] = Property(ctx->getBelName(pinBel).str(ctx)); } } } } flush_cells(); } // Convert Xilinx IO buffer primitives into IOB cells. void convert_iob() { enum IoStdKind { // Single ended, settable drive. IOSTD_SINGLE_DRIVE, // Single ended. IOSTD_SINGLE, // Pseudo-differential. IOSTD_PSEUDO_DIFF, // True differential. IOSTD_DIFF, }; std::map iostds; iostds["LVTTL"] = IOSTD_SINGLE_DRIVE; iostds["LVCMOS33"] = IOSTD_SINGLE_DRIVE; iostds["LVCMOS25"] = IOSTD_SINGLE_DRIVE; iostds["LVCMOS18"] = IOSTD_SINGLE_DRIVE; iostds["LVCMOS15"] = IOSTD_SINGLE_DRIVE; iostds["LVCMOS12"] = IOSTD_SINGLE_DRIVE; iostds["LVCMOS18_JEDEC"] = IOSTD_SINGLE_DRIVE; iostds["LVCMOS15_JEDEC"] = IOSTD_SINGLE_DRIVE; iostds["LVCMOS12_JEDEC"] = IOSTD_SINGLE_DRIVE; iostds["PCI33_3"] = IOSTD_SINGLE; iostds["PCI66_3"] = IOSTD_SINGLE; iostds["SDIO"] = IOSTD_SINGLE; iostds["MOBILE_DDR"] = IOSTD_SINGLE; iostds["DIFF_MOBILE_DDR"] = IOSTD_PSEUDO_DIFF; iostds["I2C"] = IOSTD_SINGLE; iostds["SMBUS"] = IOSTD_SINGLE; iostds["HSTL_I"] = IOSTD_SINGLE; iostds["DIFF_HSTL_I"] = IOSTD_PSEUDO_DIFF; iostds["HSTL_I_18"] = IOSTD_SINGLE; iostds["DIFF_HSTL_I_18"] = IOSTD_PSEUDO_DIFF; iostds["HSTL_II"] = IOSTD_SINGLE; iostds["DIFF_HSTL_II"] = IOSTD_PSEUDO_DIFF; iostds["HSTL_II_18"] = IOSTD_SINGLE; iostds["DIFF_HSTL_II_18"] = IOSTD_PSEUDO_DIFF; iostds["HSTL_III"] = IOSTD_SINGLE; iostds["DIFF_HSTL_III"] = IOSTD_PSEUDO_DIFF; iostds["HSTL_III_18"] = IOSTD_SINGLE; iostds["DIFF_HSTL_III_18"] = IOSTD_PSEUDO_DIFF; iostds["SSTL3_I"] = IOSTD_SINGLE; iostds["DIFF_SSTL3_I"] = IOSTD_PSEUDO_DIFF; iostds["SSTL2_I"] = IOSTD_SINGLE; iostds["DIFF_SSTL2_I"] = IOSTD_PSEUDO_DIFF; iostds["SSTL18_I"] = IOSTD_SINGLE; iostds["DIFF_SSTL18_I"] = IOSTD_PSEUDO_DIFF; iostds["SSTL3_II"] = IOSTD_SINGLE; iostds["DIFF_SSTL3_II"] = IOSTD_PSEUDO_DIFF; iostds["SSTL2_II"] = IOSTD_SINGLE; iostds["DIFF_SSTL2_II"] = IOSTD_PSEUDO_DIFF; iostds["SSTL18_II"] = IOSTD_SINGLE; iostds["DIFF_SSTL18_II"] = IOSTD_PSEUDO_DIFF; iostds["SSTL15_II"] = IOSTD_SINGLE; iostds["DIFF_SSTL15_II"] = IOSTD_PSEUDO_DIFF; iostds["BLVDS_25"] = IOSTD_PSEUDO_DIFF; iostds["LVPECL_25"] = IOSTD_PSEUDO_DIFF; iostds["LVPECL_33"] = IOSTD_PSEUDO_DIFF; iostds["DISPLAY_PORT"] = IOSTD_PSEUDO_DIFF; iostds["LVDS_33"] = IOSTD_DIFF; iostds["LVDS_25"] = IOSTD_DIFF; iostds["MINI_LVDS_33"] = IOSTD_DIFF; iostds["MINI_LVDS_25"] = IOSTD_DIFF; iostds["RSDS_33"] = IOSTD_DIFF; iostds["RSDS_25"] = IOSTD_DIFF; iostds["PPDS_33"] = IOSTD_DIFF; iostds["PPDS_25"] = IOSTD_DIFF; iostds["TMDS_33"] = IOSTD_DIFF; iostds["TML_33"] = IOSTD_DIFF; log_info("Converting IOBs...\n"); for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; IdString port_i, port_o, port_t, port_ib; IdString port_pad_m, port_pad_s; bool diff = false; if (ci->type == ctx->id("IBUF") || ci->type == ctx->id("IBUFG")) { port_i = ctx->id("O"); port_pad_m = ctx->id("I"); } else if (ci->type == ctx->id("IBUFDS") || ci->type == ctx->id("IBUFGDS")) { port_i = ctx->id("O"); port_pad_m = ctx->id("I"); port_pad_s = ctx->id("IB"); diff = true; } else if (ci->type == ctx->id("IBUFDS_DIFF_OUT") || ci->type == ctx->id("IBUFGDS_DIFF_OUT")) { port_i = ctx->id("O"); port_ib = ctx->id("OB"); port_pad_m = ctx->id("I"); port_pad_s = ctx->id("IB"); diff = true; } else if (ci->type == ctx->id("IOBUF")) { port_i = ctx->id("O"); port_o = ctx->id("I"); port_t = ctx->id("T"); port_pad_m = ctx->id("IO"); } else if (ci->type == ctx->id("IOBUFDS")) { port_i = ctx->id("O"); port_o = ctx->id("I"); port_t = ctx->id("T"); port_pad_m = ctx->id("IO"); port_pad_s = ctx->id("IOB"); diff = true; } else if (ci->type == ctx->id("OBUF")) { port_o = ctx->id("I"); port_pad_m = ctx->id("O"); } else if (ci->type == ctx->id("OBUFDS")) { port_o = ctx->id("I"); port_pad_m = ctx->id("O"); port_pad_s = ctx->id("OB"); diff = true; } else if (ci->type == ctx->id("OBUFT")) { port_o = ctx->id("I"); port_t = ctx->id("T"); port_pad_m = ctx->id("O"); } else if (ci->type == ctx->id("OBUFTDS")) { port_o = ctx->id("I"); port_t = ctx->id("T"); port_pad_m = ctx->id("O"); port_pad_s = ctx->id("OB"); diff = true; } else { // Not an IO buffer. continue; } // Get the nextpnr-inserted buffers. CellInfo *nxb_m = fetch_nxio(ci, port_pad_m); CellInfo *nxb_s = nullptr; if (port_pad_s != IdString()) nxb_s = fetch_nxio(ci, port_pad_s); if (!nxb_m) log_error("Buffer %s not connected to port.\n", ci->name.c_str(ctx)); packed_cells.insert(nxb_m->name); if (nxb_s) packed_cells.insert(nxb_s->name); // Merge UCF constraints into the buffer. for (const auto ¶m : nxb_m->params) ci->params[param.first] = param.second; for (const auto &attr : nxb_m->attrs) ci->attrs[attr.first] = attr.second; std::string iostd; auto iostd_attr = ci->params.find(ctx->id("IOSTANDARD")); if (iostd_attr != ci->params.end()) { iostd = iostd_attr->second.as_string(); } else { // Hm. if (diff) iostd = "LVDS_33"; else iostd = "LVCMOS33"; } if (iostds.find(iostd) == iostds.end()) log_error("Unknown IO standard %s for buffer %s", iostd.c_str(), ci->name.c_str(ctx)); enum IoStdKind kind = iostds[iostd]; if (kind == IOSTD_PSEUDO_DIFF || kind == IOSTD_DIFF) { diff = true; } else { if (diff) { log_error("Single-ended IO standard %s for differential buffer %s", iostd.c_str(), ci->name.c_str(ctx)); } } // Create the buffer cells. std::unique_ptr iobm_cell = create_leuctra_cell(ctx, ctx->id("IOB"), ci->name.str(ctx) + "$iob"); new_cells.push_back(std::move(iobm_cell)); CellInfo *iobm = new_cells.back().get(); CellInfo *iobs = nullptr; if (diff) { std::unique_ptr iobs_cell = create_leuctra_cell(ctx, ctx->id("IOB"), ci->name.str(ctx) + "$iobs"); new_cells.push_back(std::move(iobm_cell)); iobs = new_cells.back().get(); if (kind == IOSTD_DIFF) { iobm->params[ctx->id("__MODE__")] = Property("IOBM"); iobs->params[ctx->id("__MODE__")] = Property("IOBS"); } else { iobm->params[ctx->id("__MODE__")] = Property("IOB"); iobs->params[ctx->id("__MODE__")] = Property("IOB"); } } else { iobm->params[ctx->id("__MODE__")] = Property("IOB"); } // Deal with input path. if (port_i != IdString()) { replace_port(ci, port_i, iobm, ctx->id("I")); if (diff) { iobs->params[ctx->id("PADOUTUSED")] = Property("0"); connect_ports(ctx, iobs, ctx->id("PADOUT"), iobm, ctx->id("DIFFI_IN")); } iobm->params[ctx->id("IMUX")] = Property("I"); iobm->params[ctx->id("BYPASS_MUX")] = Property("I"); iobm->params[ctx->id("ISTANDARD")] = Property(iostd); if (iobs) iobm->params[ctx->id("ISTANDARD")] = Property(iostd); } if (port_ib != IdString()) { replace_port(ci, port_ib, iobs, ctx->id("I")); if (diff) { iobm->params[ctx->id("PADOUTUSED")] = Property("0"); connect_ports(ctx, iobm, ctx->id("PADOUT"), iobs, ctx->id("DIFFI_IN")); } iobs->params[ctx->id("IMUX")] = Property("I"); iobs->params[ctx->id("BYPASS_MUX")] = Property("I"); } // Deal with output path. if (port_o != IdString()) { NetInfo *net_o = ci->ports.at(port_o).net; NetInfo *net_t = nullptr; if (port_t != IdString()) net_t = ci->ports.at(port_t).net; connect_port(ctx, net_o, iobm, ctx->id("O")); if (kind == IOSTD_PSEUDO_DIFF) { // XXX NPNR_ASSERT_FALSE("PSEUDO DIFF OUTPUT"); connect_port(ctx, net_o, iobs, ctx->id("O")); } disconnect_port(ctx, ci, port_o); if (net_t) { connect_port(ctx, net_t, iobm, ctx->id("T")); if (kind == IOSTD_PSEUDO_DIFF) connect_port(ctx, net_t, iobs, ctx->id("T")); disconnect_port(ctx, ci, port_t); } iobm->params[ctx->id("OSTANDARD")] = Property(iostd); if (iobs) iobs->params[ctx->id("OSTANDARD")] = Property(iostd); iobm->params[ctx->id("OUSED")] = Property("0"); if (port_t != IdString()) iobm->params[ctx->id("TUSED")] = Property("0"); if (kind == IOSTD_PSEUDO_DIFF) { iobs->params[ctx->id("OUSED")] = Property("0"); if (port_t != IdString()) iobs->params[ctx->id("TUSED")] = Property("0"); } if (kind == IOSTD_SINGLE_DRIVE) { if (ci->params.count(ctx->id("DRIVE"))) iobm->params[ctx->id("DRIVEATTRBOX")] = ci->params[ctx->id("DRIVE")]; else iobm->params[ctx->id("DRIVEATTRBOX")] = Property("12"); if (ci->params.count(ctx->id("SLEW"))) iobm->params[ctx->id("SLEW")] = ci->params[ctx->id("SLEW")]; else iobm->params[ctx->id("SLEW")] = Property("SLOW"); } if (kind == IOSTD_DIFF) { connect_ports(ctx, iobm, ctx->id("DIFFO_OUT"), iobs, ctx->id("DIFFO_IN")); iobs->params[ctx->id("OUTMUX")] = Property("0"); } } if (ci->params.count(ctx->id("PULLTYPE"))) iobm->params[ctx->id("PULLTYPE")] = ci->params[ctx->id("PULLTYPE")]; if (ci->params.count(ctx->id("SUSPEND"))) iobm->params[ctx->id("SUSPEND")] = ci->params[ctx->id("SUSPEND")]; if (ci->params.count(ctx->id("PRE_EMPHASIS"))) iobm->params[ctx->id("PRE_EMPHASIS")] = ci->params[ctx->id("PRE_EMPHASIS")]; auto loc_attr = ci->attrs.find(ctx->id("LOC")); if (loc_attr != ci->attrs.end()) { std::string pin = loc_attr->second.as_string(); BelId pinBel = ctx->getPackagePinBel(pin); if (pinBel == BelId()) { log_error("IO pin '%s' constrained to pin '%s', which does not exist for package '%s'.\n", ci->name.c_str(ctx), pin.c_str(), ctx->args.package.c_str()); } else { log_info("pin '%s' constrained to Bel '%s'.\n", ci->name.c_str(ctx), ctx->getBelName(pinBel).c_str(ctx)); } iobm->attrs[ctx->id("BEL")] = Property(ctx->getBelName(pinBel).str(ctx)); if (iobs) { BelId opinBel = pinBel; if ((pinBel.index & 1) && kind == IOSTD_DIFF) { log_error("True differential IO pin '%s' constrained to pin '%s', which is not a master pin.\n", ci->name.c_str(ctx), pin.c_str()); } opinBel.index ^= 1; iobs->attrs[ctx->id("BEL")] = Property(ctx->getBelName(opinBel).str(ctx)); } } else { if (iobs) { // XXX NPNR_ASSERT_FALSE("UNCONSTRAINED DIFF PAIR"); } } packed_cells.insert(ci->name); } flush_cells(); } // Ensure ilogic/ologic cell for every IOB that needs one. void pack_iologic() { log_info("Packing ILOGICs/OLOGICs...\n"); for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (is_iob(ctx, ci)) { NetInfo *net_i = ci->ports.at(ctx->id("I")).net; if (net_i != nullptr) { // Insert ILOGIC. std::unique_ptr ilogic = create_leuctra_cell(ctx, ctx->id("ILOGIC2"), ci->name.str(ctx) + "$ilogic"); insert_ilogic_pass(ctx, ci, ilogic.get()); new_cells.push_back(std::move(ilogic)); } NetInfo *net_o = ci->ports.at(ctx->id("O")).net; if (net_o != nullptr) { // Insert OLOGIC. std::unique_ptr ologic = create_leuctra_cell(ctx, ctx->id("OLOGIC2"), ci->name.str(ctx) + "$ologic"); insert_ologic_pass(ctx, ci, ologic.get()); new_cells.push_back(std::move(ologic)); } auto bel_attr = ci->attrs.find(ctx->id("BEL")); if (bel_attr != ci->attrs.end()) { BelId bel = ctx->getBelByName(ctx->id(ci->attrs[ctx->id("BEL")].as_string())); for (auto &child : ci->constr_children) { BelId child_bel = ctx->getRelatedBel(bel, child->constr_spec); child->attrs[ctx->id("BEL")] = Property(ctx->getBelName(child_bel).str(ctx)); child->constr_parent = nullptr; child->constr_spec = -1; } ci->constr_children.clear(); } } } flush_cells(); } // Convert FFs/latches to LEUCTRA_FFs. void pack_bram() { log_info("Packing Block RAMs...\n"); for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (ci->type == ctx->id("RAMB16BWER")) { fixup_ramb16(ctx, ci, new_cells, packed_cells); } else if (ci->type == ctx->id("RAMB8BWER")) { fixup_ramb8(ctx, ci, new_cells, packed_cells); } } flush_cells(); } // Convert FFs/latches to LEUCTRA_FFs. void pack_ff() { log_info("Packing FFs...\n"); for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (ci->type == ctx->id("FDRE") || ci->type == ctx->id("FDSE") || ci->type == ctx->id("FDCE") || ci->type == ctx->id("FDPE") || ci->type == ctx->id("FDRE_1") || ci->type == ctx->id("FDSE_1") || ci->type == ctx->id("FDCE_1") || ci->type == ctx->id("FDPE_1") || ci->type == ctx->id("LDCE") || ci->type == ctx->id("LDPE") || ci->type == ctx->id("LDCE_1") || ci->type == ctx->id("LDPE_1")) { std::unique_ptr ff_cell = create_leuctra_cell(ctx, ctx->id("LEUCTRA_FF"), ci->name.str(ctx) + "$ff"); convert_ff(ctx, ci, ff_cell.get(), new_cells, packed_cells); new_cells.push_back(std::move(ff_cell)); packed_cells.insert(ci->name); } } flush_cells(); } // Convert RAMs to LEUCTRA_LCs. void pack_ram() { log_info("Packing distributed RAM...\n"); for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (ci->type == ctx->id("RAM32X1D") || ci->type == ctx->id("RAM64X1D")) { int sz = ci->type == ctx->id("RAM32X1D") ? 5 : 6; CellInfo *lcs[2]; for (int i = 0; i < 2; i++) { std::unique_ptr lut_cell = create_leuctra_cell(ctx, ctx->id("LEUCTRA_LC"), ci->name.str(ctx) + "$lc" + std::to_string(i)); new_cells.push_back(std::move(lut_cell)); lcs[i] = new_cells.back().get(); lcs[i]->params[ctx->id("MODE")] = Property("RAM" + std::to_string(sz)); if (sz == 6) lcs[i]->params[ctx->id("DIMUX")] = Property("XI"); lcs[i]->attrs[ctx->id("NEEDS_M")] = Property(true); } NetInfo *net; bool net_inv; if (get_invertible_port(ctx, ci, ctx->id("WCLK"), false, true, net, net_inv)) { set_invertible_port(ctx, lcs[0], ctx->id("CLK"), net, net_inv, true, new_cells); set_invertible_port(ctx, lcs[1], ctx->id("CLK"), net, net_inv, true, new_cells); } net = ci->ports[ctx->id("WE")].net; disconnect_port(ctx, ci, ctx->id("WE")); connect_port(ctx,net, lcs[0], ctx->id("WE")); connect_port(ctx,net, lcs[1], ctx->id("WE")); for (int i = 0; i < sz; i++) { IdString pname = ctx->id("A" + std::to_string(i)); net = ci->ports[pname].net; disconnect_port(ctx, ci, pname); connect_port(ctx,net, lcs[0], ctx->id("WA" + std::to_string(i+1))); connect_port(ctx,net, lcs[1], ctx->id("WA" + std::to_string(i+1))); connect_port(ctx,net, lcs[1], ctx->id("RA" + std::to_string(i+1))); pname = ctx->id("DPRA" + std::to_string(i)); net = ci->ports[pname].net; disconnect_port(ctx, ci, pname); connect_port(ctx,net, lcs[0], ctx->id("RA" + std::to_string(i+1))); } net = ci->ports[ctx->id("D")].net; disconnect_port(ctx, ci, ctx->id("D")); if (sz == 5) { connect_port(ctx,net, lcs[0], ctx->id("DDI5")); connect_port(ctx,net, lcs[1], ctx->id("DDI5")); set_const_port(ctx, lcs[0], ctx->id("RA6"), true, new_cells); set_const_port(ctx, lcs[1], ctx->id("RA6"), true, new_cells); } else { connect_port(ctx,net, lcs[0], ctx->id("XI")); connect_port(ctx,net, lcs[1], ctx->id("XI")); } replace_port(ci, ctx->id("SPO"), lcs[1], ctx->id("O6")); replace_port(ci, ctx->id("DPO"), lcs[0], ctx->id("O6")); Property v = ci->params[ctx->id("INIT")]; Property nv(0, 64); if (sz == 5) { for (int i = 0; i < 64; i++) nv.str[i] = v.str[i%32]; nv.update_intval(); } else { nv = v; } lcs[0]->params[ctx->id("INIT")] = nv; lcs[1]->params[ctx->id("INIT")] = nv; lcs[1]->attrs[ctx->id("LOCMASK")] = Property(0x8, 4); lcs[0]->constr_parent = lcs[1]; lcs[0]->constr_z = -3; lcs[1]->constr_children.push_back(lcs[0]); packed_cells.insert(ci->name); } else if (ci->type == ctx->id("RAM128X1D")) { CellInfo *lcs[4]; for (int i = 0; i < 4; i++) { std::unique_ptr lut_cell = create_leuctra_cell(ctx, ctx->id("LEUCTRA_LC"), ci->name.str(ctx) + "$lc" + std::to_string(i)); new_cells.push_back(std::move(lut_cell)); lcs[i] = new_cells.back().get(); lcs[i]->params[ctx->id("MODE")] = Property("RAM7"); lcs[i]->params[ctx->id("DIMUX")] = Property("DDI7"); lcs[i]->attrs[ctx->id("NEEDS_M")] = Property(true); } NetInfo *net; bool net_inv; if (get_invertible_port(ctx, ci, ctx->id("WCLK"), false, true, net, net_inv)) { for (int i = 0; i < 4; i++) set_invertible_port(ctx, lcs[i], ctx->id("CLK"), net, net_inv, true, new_cells); } net = ci->ports[ctx->id("WE")].net; disconnect_port(ctx, ci, ctx->id("WE")); for (int i = 0; i < 4; i++) connect_port(ctx,net, lcs[i], ctx->id("WE")); for (int i = 0; i < 7; i++) { IdString pname = ctx->id("A[" + std::to_string(i) + "]"); net = ci->ports[pname].net; disconnect_port(ctx, ci, pname); for (int j = 0; j < 4; j++) connect_port(ctx,net, lcs[j], ctx->id("WA" + std::to_string(i+1))); if (i < 6) { for (int j = 2; j < 4; j++) connect_port(ctx,net, lcs[j], ctx->id("RA" + std::to_string(i+1))); } else { connect_port(ctx,net, lcs[2], ctx->id("XI")); } pname = ctx->id("DPRA[" + std::to_string(i) + "]"); net = ci->ports[pname].net; disconnect_port(ctx, ci, pname); if (i < 6) { for (int j = 0; j < 2; j++) connect_port(ctx,net, lcs[j], ctx->id("RA" + std::to_string(i+1))); } else { connect_port(ctx,net, lcs[0], ctx->id("XI")); } } net = ci->ports[ctx->id("D")].net; disconnect_port(ctx, ci, ctx->id("D")); for (int i = 0; i < 4; i++) connect_port(ctx,net, lcs[i], ctx->id("DDI7")); replace_port(ci, ctx->id("SPO"), lcs[2], ctx->id("MO")); replace_port(ci, ctx->id("DPO"), lcs[0], ctx->id("MO")); connect_ports(ctx, lcs[3], ctx->id("O6"), lcs[2], ctx->id("DMI0")); connect_ports(ctx, lcs[2], ctx->id("O6"), lcs[2], ctx->id("DMI1")); connect_ports(ctx, lcs[1], ctx->id("O6"), lcs[0], ctx->id("DMI0")); connect_ports(ctx, lcs[0], ctx->id("O6"), lcs[0], ctx->id("DMI1")); Property v = ci->params[ctx->id("INIT")]; Property nv(0, 64); for (int i = 0; i < 64; i++) nv.str[i] = v.str[i]; nv.update_intval(); lcs[3]->params[ctx->id("INIT")] = nv; lcs[1]->params[ctx->id("INIT")] = nv; for (int i = 0; i < 64; i++) nv.str[i] = v.str[i + 64]; nv.update_intval(); lcs[2]->params[ctx->id("INIT")] = nv; lcs[0]->params[ctx->id("INIT")] = nv; lcs[3]->attrs[ctx->id("LOCMASK")] = Property(0x8, 4); for (int i = 0; i < 3; i++) { lcs[i]->constr_parent = lcs[3]; lcs[i]->constr_z = (i - 3) * 3; lcs[3]->constr_children.push_back(lcs[i]); } packed_cells.insert(ci->name); } else if (ci->type == ctx->id("RAM32M")) { CellInfo *lcs[4]; for (int i = 0; i < 4; i++) { std::unique_ptr lut_cell = create_leuctra_cell(ctx, ctx->id("LEUCTRA_LC"), ci->name.str(ctx) + "$lc" + std::to_string(i)); new_cells.push_back(std::move(lut_cell)); lcs[i] = new_cells.back().get(); lcs[i]->params[ctx->id("MODE")] = Property("RAM5"); lcs[i]->params[ctx->id("DIMUX")] = Property("XI"); lcs[i]->attrs[ctx->id("NEEDS_M")] = Property(true); } NetInfo *net; bool net_inv; if (get_invertible_port(ctx, ci, ctx->id("WCLK"), false, true, net, net_inv)) { for (int i = 0; i < 4; i++) set_invertible_port(ctx, lcs[i], ctx->id("CLK"), net, net_inv, true, new_cells); } net = ci->ports[ctx->id("WE")].net; disconnect_port(ctx, ci, ctx->id("WE")); for (int i = 0; i < 4; i++) connect_port(ctx,net, lcs[i], ctx->id("WE")); for (int i = 0; i < 4; i++) { std::string l{"ABCD"[i]}; for (int j = 0; j < 5; j++) { IdString pname = ctx->id("ADDR" + l + "[" + std::to_string(j) + "]"); net = ci->ports[pname].net; disconnect_port(ctx, ci, pname); if (i == 3) { for (int k = 0; k < 4; k++) connect_port(ctx,net, lcs[k], ctx->id("WA" + std::to_string(j+1))); } connect_port(ctx,net, lcs[i], ctx->id("RA" + std::to_string(j+1))); } set_const_port(ctx, lcs[i], ctx->id("RA6"), true, new_cells); replace_port(ci, ctx->id("DI" + l + "[0]"), lcs[i], ctx->id("XI")); replace_port(ci, ctx->id("DI" + l + "[1]"), lcs[i], ctx->id("DDI5")); replace_port(ci, ctx->id("DO" + l + "[0]"), lcs[i], ctx->id("O5")); replace_port(ci, ctx->id("DO" + l + "[1]"), lcs[i], ctx->id("O6")); Property v = ci->params[ctx->id("INIT_" + l)]; Property nv(0, 64); for (int j = 0; j < 32; j++) for (int k = 0; k < 2; k++) nv.str[j | k << 5] = v.str[j << 1 | k]; nv.update_intval(); lcs[i]->params[ctx->id("INIT")] = nv; } lcs[3]->attrs[ctx->id("LOCMASK")] = Property(0x8, 4); for (int i = 0; i < 3; i++) { lcs[i]->constr_parent = lcs[3]; lcs[i]->constr_z = (i - 3) * 3; lcs[3]->constr_children.push_back(lcs[i]); } packed_cells.insert(ci->name); } } flush_cells(); } // Convert CARRY4s to LEUCTRA_LCs. void pack_carry() { log_info("Packing CARRY4s...\n"); // CARRY4 -> next CARRY4 in the chain std::unordered_map chain; // CARRY4s that are chain starts. std::vector init; for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (ci->type == ctx->id("CARRY4")) { NetInfo *net = ci->ports.at(ctx->id("CI")).net; CellInfo *prev = nullptr; if (net) prev = net->driver.cell; bool cval; if (!prev || get_const_val(ctx, net, cval)) { init.push_back(ci); } else { if (prev->type != ctx->id("CARRY4") || net->driver.port != ctx->id("CO[3]")) log_error("CARRY4 %s has weird CI: %s (%s) %s", ci->name.c_str(ctx), prev->name.c_str(ctx), prev->type.c_str(ctx), net->driver.port.c_str(ctx)); if (chain.count(prev)) log_error("Split carry chain: %s %s %s", prev->name.c_str(ctx), ci->name.c_str(ctx), chain[prev]->name.c_str(ctx)); chain[prev] = ci; } } } for (auto cell : init) { CellInfo *cur = cell; CellInfo *link = nullptr; while (cur) { link = convert_carry4(ctx, cur, link, new_cells, packed_cells); cur = chain[cur]; } } flush_cells(); } // Convert MUXF8s to LEUCTRA_LCs. void pack_muxf8() { log_info("Packing MUXF8s...\n"); for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (ci->type == ctx->id("MUXF8")) { NetInfo *net = ci->ports.at(ctx->id("O")).net; convert_muxf8(ctx, net, ci->name.str(ctx) + "$lc", new_cells, packed_cells); } } flush_cells(); } // Convert MUXF7s to LEUCTRA_LCs. void pack_muxf7() { log_info("Packing MUXF7s...\n"); for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (ci->type == ctx->id("MUXF7")) { NetInfo *net = ci->ports.at(ctx->id("O")).net; convert_muxf7(ctx, net, ci->name.str(ctx) + "$lc", new_cells, packed_cells); } } flush_cells(); } // Convert LUTs to LEUCTRA_LCs. void pack_lut() { log_info("Packing LUTs...\n"); for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (is_xilinx_lut(ctx, ci)) { NetInfo *net = ci->ports.at(ctx->id("O")).net; convert_lut(ctx, net, ci->name.str(ctx) + "$lc", new_cells, packed_cells); } } flush_cells(); } // Convert misc cell types. void pack_misc() { log_info("Converting misc cell types...\n"); for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (ci->type == ctx->id("BUFG")) { ci->type = ctx->id("BUFGMUX"); rename_port(ctx, ci, ctx->id("I"), ctx->id("I0")); set_const_port(ctx, ci, ctx->id("S"), true, new_cells); ci->params[ctx->id("SINV")] = Property("S_B"); } } flush_cells(); } // Merge a net into a constant net void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval) { orig->driver.cell = nullptr; for (auto user : orig->users) { if (user.cell != nullptr) { CellInfo *uc = user.cell; if (ctx->verbose) log_info("%s user %s\n", orig->name.c_str(ctx), uc->name.c_str(ctx)); uc->ports[user.port].net = constnet; constnet->users.push_back(user); } } orig->users.clear(); } // Pack constants (simple implementation) void pack_constants() { log_info("Packing constants..\n"); std::unique_ptr gnd_cell = create_leuctra_cell(ctx, ctx->id("LEUCTRA_LC"), "$PACKER_GND"); gnd_cell->params[ctx->id("INIT")] = Property(0, 64); std::unique_ptr gnd_net = std::unique_ptr(new NetInfo); gnd_net->name = ctx->id("$PACKER_GND_NET"); gnd_net->driver.cell = gnd_cell.get(); gnd_net->driver.port = ctx->id("O6"); gnd_cell->ports.at(ctx->id("O6")).net = gnd_net.get(); gnd_cell->attrs[ctx->id("CONST")] = Property(false); std::unique_ptr vcc_cell = create_leuctra_cell(ctx, ctx->id("LEUCTRA_LC"), "$PACKER_VCC"); vcc_cell->params[ctx->id("INIT")] = Property(-1, 64); std::unique_ptr vcc_net = std::unique_ptr(new NetInfo); vcc_net->name = ctx->id("$PACKER_VCC_NET"); vcc_net->driver.cell = vcc_cell.get(); vcc_net->driver.port = ctx->id("O6"); vcc_cell->ports.at(ctx->id("O6")).net = vcc_net.get(); vcc_cell->attrs[ctx->id("CONST")] = Property(true); std::vector dead_nets; bool gnd_used = false, vcc_used = false; for (auto net : sorted(ctx->nets)) { NetInfo *ni = net.second; if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("GND")) { IdString drv_cell = ni->driver.cell->name; set_net_constant(ctx, ni, gnd_net.get(), false); gnd_used = true; dead_nets.push_back(net.first); ctx->cells.erase(drv_cell); } else if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("VCC")) { IdString drv_cell = ni->driver.cell->name; set_net_constant(ctx, ni, vcc_net.get(), true); vcc_used = true; dead_nets.push_back(net.first); ctx->cells.erase(drv_cell); } } if (gnd_used) { ctx->cells[gnd_cell->name] = std::move(gnd_cell); ctx->nets[gnd_net->name] = std::move(gnd_net); } if (vcc_used) { ctx->cells[vcc_cell->name] = std::move(vcc_cell); ctx->nets[vcc_net->name] = std::move(vcc_net); } for (auto dn : dead_nets) { ctx->nets.erase(dn); } } public: void pack() { //insert_iob(); convert_iob(); pack_iologic(); pack_bram(); pack_ff(); pack_ram(); // pack_srl(); pack_carry(); pack_muxf8(); pack_muxf7(); // clean_inv(); pack_lut(); // pack_lc_ff(); pack_misc(); pack_constants(); } private: Context *ctx; std::unordered_set packed_cells; std::vector> new_cells; }; // Main pack function bool Arch::pack() { Context *ctx = getCtx(); try { log_break(); LeuctraPacker(ctx).pack(); ctx->settings[ctx->id("pack")] = 1; log_info("Checksum: 0x%08x\n", ctx->checksum()); // XXX //assignArchInfo(); return true; } catch (log_execution_error_exception) { // XXX //assignArchInfo(); return false; } } NEXTPNR_NAMESPACE_END