nextpnr/himbaechel/uarch/xilinx/fasm.cc

1657 lines
72 KiB
C++
Raw Normal View History

/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2019-2023 gatecat <gatecat@ds0.me>
* Copyright (C) 2023 Hans Baier <hansfbaier@gmail.com>
*
* 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 <boost/algorithm/string.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <fstream>
#include <regex>
#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<std::string> fasm_ctx;
dict<int, std::vector<PipId>> pips_by_tile;
dict<IdString, pool<IdString>> 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<bool> &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<bool> 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<PseudoPipKey, std::vector<std::string>> 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<std::string>{"", "0", "1"} : std::vector<std::string>{"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<std::string>{"0"} : std::vector<std::string>{"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<const XlnxPipExtraDataPOD *>(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<IdString> 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<bool> get_lut_init(CellInfo *lut6, CellInfo *lut5)
{
std::vector<bool> bits(64, false);
std::vector<IdString> 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<int, std::vector<std::string>> phys_to_log;
dict<std::string, int> 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<const XlnxPipExtraDataPOD *>(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 &lts = 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 &lts = 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 &lts = 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<int> 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<int, BankIoConfig> 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<std::string> used_wires_starting_with(int tile, const std::string &prefix, bool is_source)
{
std::vector<std::string> 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<std::string> all_gclk;
dict<int, std::set<std::string>> 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<bool> 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<bool>(18, true));
write_vector("ZINIT_B[17:0]", std::vector<bool>(18, true));
write_vector("ZSRVAL_A[17:0]", std::vector<bool>(18, true));
write_vector("ZSRVAL_B[17:0]", std::vector<bool>(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<bool> 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<bool> 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<std::string> 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