aboutsummaryrefslogtreecommitdiffstats
path: root/mistral
diff options
context:
space:
mode:
authorgatecat <gatecat@ds0.me>2021-05-15 22:37:19 +0100
committerGitHub <noreply@github.com>2021-05-15 22:37:19 +0100
commit47b4e42b1c0b849dc73a06357ad7e5cb24e0cbd7 (patch)
tree2a1a642e6bd577851161e0806453325ad777a302 /mistral
parent1b5767928de16b1df2e8d90066023e3cd076d40d (diff)
parent3eeb2b20ebd3e527fb82a46774f4584575a3a9e1 (diff)
downloadnextpnr-47b4e42b1c0b849dc73a06357ad7e5cb24e0cbd7.tar.gz
nextpnr-47b4e42b1c0b849dc73a06357ad7e5cb24e0cbd7.tar.bz2
nextpnr-47b4e42b1c0b849dc73a06357ad7e5cb24e0cbd7.zip
Merge pull request #707 from gatecat/cyclonev
mistral: Initial Cyclone V support
Diffstat (limited to 'mistral')
-rw-r--r--mistral/arch.cc487
-rw-r--r--mistral/arch.h550
-rw-r--r--mistral/arch_pybindings.cc87
-rw-r--r--mistral/arch_pybindings.h125
-rw-r--r--mistral/archdefs.cc33
-rw-r--r--mistral/archdefs.h236
-rw-r--r--mistral/base_bitstream.cc100
-rw-r--r--mistral/bitstream.cc361
-rw-r--r--mistral/constids.inc78
-rw-r--r--mistral/family.cmake14
-rw-r--r--mistral/globals.cc46
-rw-r--r--mistral/io.cc61
-rw-r--r--mistral/lab.cc969
-rw-r--r--mistral/main.cc105
-rw-r--r--mistral/pack.cc365
-rw-r--r--mistral/pins.cc67
-rw-r--r--mistral/qsf.cc281
17 files changed, 3965 insertions, 0 deletions
diff --git a/mistral/arch.cc b/mistral/arch.cc
new file mode 100644
index 00000000..cfa3e8b3
--- /dev/null
+++ b/mistral/arch.cc
@@ -0,0 +1,487 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 Lofty <dan.ravensloft@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 <algorithm>
+
+#include "log.h"
+#include "nextpnr.h"
+
+#include "placer1.h"
+#include "placer_heap.h"
+#include "router1.h"
+#include "router2.h"
+#include "timing.h"
+#include "util.h"
+
+#include "cyclonev.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+using namespace mistral;
+
+void IdString::initialize_arch(const BaseCtx *ctx)
+{
+#define X(t) initialize_add(ctx, #t, ID_##t);
+
+#include "constids.inc"
+
+#undef X
+}
+
+Arch::Arch(ArchArgs args)
+{
+ this->args = args;
+ this->cyclonev = mistral::CycloneV::get_model(args.device, args.mistral_root);
+ NPNR_ASSERT(this->cyclonev != nullptr);
+
+ // Setup fast identifier maps
+ for (int i = 0; i < 1024; i++) {
+ IdString int_id = id(stringf("%d", i));
+ int2id.push_back(int_id);
+ id2int[int_id] = i;
+ }
+
+ for (int t = int(CycloneV::NONE); t <= int(CycloneV::DCMUX); t++) {
+ IdString rnode_id = id(CycloneV::rnode_type_names[t]);
+ rn_t2id.push_back(rnode_id);
+ id2rn_t[rnode_id] = CycloneV::rnode_type_t(t);
+ }
+
+ log_info("Initialising bels...\n");
+ bels_by_tile.resize(cyclonev->get_tile_sx() * cyclonev->get_tile_sy());
+ for (int x = 0; x < cyclonev->get_tile_sx(); x++) {
+ for (int y = 0; y < cyclonev->get_tile_sy(); y++) {
+ CycloneV::pos_t pos = cyclonev->xy2pos(x, y);
+
+ for (CycloneV::block_type_t bel : cyclonev->pos_get_bels(pos)) {
+ switch (bel) {
+ case CycloneV::block_type_t::LAB:
+ create_lab(x, y);
+ break;
+ default:
+ continue;
+ }
+ }
+ }
+ }
+
+ for (auto gpio_pos : cyclonev->gpio_get_pos())
+ create_gpio(CycloneV::pos2x(gpio_pos), CycloneV::pos2y(gpio_pos));
+
+ for (auto cmuxh_pos : cyclonev->cmuxh_get_pos())
+ create_clkbuf(CycloneV::pos2x(cmuxh_pos), CycloneV::pos2y(cmuxh_pos));
+
+ // This import takes about 5s, perhaps long term we can speed it up, e.g. defer to Mistral more...
+ log_info("Initialising routing graph...\n");
+ int pip_count = 0;
+ for (const auto &mux : cyclonev->dest_node_to_rmux) {
+ const auto &rmux = cyclonev->rmux_info[mux.second];
+ WireId dst_wire(mux.first);
+ for (const auto &src : rmux.sources) {
+ if (CycloneV::rn2t(src) == CycloneV::NONE)
+ continue;
+ WireId src_wire(src);
+ wires[dst_wire].wires_uphill.push_back(src_wire);
+ wires[src_wire].wires_downhill.push_back(dst_wire);
+ ++pip_count;
+ }
+ }
+
+ log_info(" imported %d wires and %d pips\n", int(wires.size()), pip_count);
+
+ BaseArch::init_cell_types();
+ BaseArch::init_bel_buckets();
+}
+
+int Arch::getTileBelDimZ(int x, int y) const
+{
+ // This seems like a reasonable upper bound
+ return 256;
+}
+
+BelId Arch::getBelByName(IdStringList name) const
+{
+ BelId bel;
+ NPNR_ASSERT(name.size() == 4);
+ int x = id2int.at(name[1]);
+ int y = id2int.at(name[2]);
+ int z = id2int.at(name[3]);
+
+ bel.pos = CycloneV::xy2pos(x, y);
+ bel.z = z;
+
+ NPNR_ASSERT(name[0] == getBelType(bel));
+
+ return bel;
+}
+
+IdStringList Arch::getBelName(BelId bel) const
+{
+ int x = CycloneV::pos2x(bel.pos);
+ int y = CycloneV::pos2y(bel.pos);
+ int z = bel.z & 0xFF;
+
+ std::array<IdString, 4> ids{
+ getBelType(bel),
+ int2id.at(x),
+ int2id.at(y),
+ int2id.at(z),
+ };
+
+ return IdStringList(ids);
+}
+
+bool Arch::isBelLocationValid(BelId bel) const
+{
+ auto &data = bel_data(bel);
+ if (data.type == id_MISTRAL_COMB) {
+ return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && check_lab_input_count(data.lab_data.lab);
+ } else if (data.type == id_MISTRAL_FF) {
+ return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && check_lab_input_count(data.lab_data.lab) &&
+ is_lab_ctrlset_legal(data.lab_data.lab);
+ }
+ return true;
+}
+
+void Arch::update_bel(BelId bel)
+{
+ auto &data = bel_data(bel);
+ if (data.type == id_MISTRAL_COMB || data.type == id_MISTRAL_FF) {
+ update_alm_input_count(data.lab_data.lab, data.lab_data.alm);
+ }
+}
+
+WireId Arch::getWireByName(IdStringList name) const
+{
+ // non-mistral wires
+ auto found_npnr = npnr_wirebyname.find(name);
+ if (found_npnr != npnr_wirebyname.end())
+ return found_npnr->second;
+ // mistral wires
+ NPNR_ASSERT(name.size() == 4);
+ CycloneV::rnode_type_t ty = id2rn_t.at(name[0]);
+ int x = id2int.at(name[1]);
+ int y = id2int.at(name[2]);
+ int z = id2int.at(name[3]);
+ return WireId(CycloneV::rnode(ty, x, y, z));
+}
+
+IdStringList Arch::getWireName(WireId wire) const
+{
+ if (wire.is_nextpnr_created()) {
+ // non-mistral wires
+ std::array<IdString, 4> ids{
+ id_WIRE,
+ int2id.at(CycloneV::rn2x(wire.node)),
+ int2id.at(CycloneV::rn2y(wire.node)),
+ wires.at(wire).name_override,
+ };
+ return IdStringList(ids);
+ } else {
+ std::array<IdString, 4> ids{
+ rn_t2id.at(CycloneV::rn2t(wire.node)),
+ int2id.at(CycloneV::rn2x(wire.node)),
+ int2id.at(CycloneV::rn2y(wire.node)),
+ int2id.at(CycloneV::rn2z(wire.node)),
+ };
+ return IdStringList(ids);
+ }
+}
+
+PipId Arch::getPipByName(IdStringList name) const
+{
+ WireId src = getWireByName(name.slice(0, 4));
+ WireId dst = getWireByName(name.slice(4, 8));
+ NPNR_ASSERT(src != WireId());
+ NPNR_ASSERT(dst != WireId());
+ return PipId(src.node, dst.node);
+}
+
+IdStringList Arch::getPipName(PipId pip) const
+{
+ return IdStringList::concat(getWireName(getPipSrcWire(pip)), getWireName(getPipDstWire(pip)));
+}
+
+std::vector<BelId> Arch::getBelsByTile(int x, int y) const
+{
+ // This should probably be redesigned, but it's a hack.
+ std::vector<BelId> bels;
+ if (x >= 0 && x < cyclonev->get_tile_sx() && y >= 0 && y < cyclonev->get_tile_sy()) {
+ for (size_t i = 0; i < bels_by_tile.at(pos2idx(x, y)).size(); i++)
+ bels.push_back(BelId(CycloneV::xy2pos(x, y), i));
+ }
+
+ return bels;
+}
+
+IdString Arch::getBelType(BelId bel) const { return bel_data(bel).type; }
+
+std::vector<IdString> Arch::getBelPins(BelId bel) const
+{
+ std::vector<IdString> pins;
+ for (auto &p : bel_data(bel).pins)
+ pins.push_back(p.first);
+ return pins;
+}
+
+bool Arch::isValidBelForCellType(IdString cell_type, BelId bel) const
+{
+ // Any combinational cell type can - theoretically - be placed at a combinational ALM bel
+ // The precise legality mechanics will be dealt with in isBelLocationValid.
+ IdString bel_type = getBelType(bel);
+ if (bel_type == id_MISTRAL_COMB)
+ return is_comb_cell(cell_type);
+ else if (bel_type == id_MISTRAL_IO)
+ return is_io_cell(cell_type);
+ else if (bel_type == id_MISTRAL_CLKENA)
+ return is_clkbuf_cell(cell_type);
+ else
+ return bel_type == cell_type;
+}
+
+BelBucketId Arch::getBelBucketForCellType(IdString cell_type) const
+{
+ if (is_comb_cell(cell_type))
+ return id_MISTRAL_COMB;
+ else if (is_io_cell(cell_type))
+ return id_MISTRAL_IO;
+ else if (is_clkbuf_cell(cell_type))
+ return id_MISTRAL_CLKENA;
+ else
+ return cell_type;
+}
+
+BelId Arch::bel_by_block_idx(int x, int y, IdString type, int block_index) const
+{
+ auto &bels = bels_by_tile.at(pos2idx(x, y));
+ for (size_t i = 0; i < bels.size(); i++) {
+ auto &bel_data = bels.at(i);
+ if (bel_data.type == type && bel_data.block_index == block_index)
+ return BelId(CycloneV::xy2pos(x, y), i);
+ }
+ return BelId();
+}
+
+BelId Arch::add_bel(int x, int y, IdString name, IdString type)
+{
+ auto &bels = bels_by_tile.at(pos2idx(x, y));
+ BelId id = BelId(CycloneV::xy2pos(x, y), bels.size());
+ all_bels.push_back(id);
+ bels.emplace_back();
+ auto &bel = bels.back();
+ bel.name = name;
+ bel.type = type;
+ // TODO: buckets (for example LABs and MLABs in the same bucket)
+ bel.bucket = type;
+ return id;
+}
+
+WireId Arch::add_wire(int x, int y, IdString name, uint64_t flags)
+{
+ std::array<IdString, 4> ids{
+ id_WIRE,
+ int2id.at(x),
+ int2id.at(y),
+ name,
+ };
+ IdStringList full_name(ids);
+ auto existing = npnr_wirebyname.find(full_name);
+ if (existing != npnr_wirebyname.end()) {
+ // Already exists, don't create anything
+ return existing->second;
+ } else {
+ // Determine a unique ID for the wire
+ int z = 0;
+ WireId id;
+ while (wires.count(id = WireId(CycloneV::rnode(CycloneV::rnode_type_t((z >> 10) + 128), x, y, (z & 0x3FF)))))
+ z++;
+ wires[id].name_override = name;
+ wires[id].flags = flags;
+ npnr_wirebyname[full_name] = id;
+ return id;
+ }
+}
+
+void Arch::reserve_route(WireId src, WireId dst)
+{
+ auto &dst_data = wires.at(dst);
+ int idx = -1;
+
+ for (int i = 0; i < int(dst_data.wires_uphill.size()); i++) {
+ if (dst_data.wires_uphill.at(i) == src) {
+ idx = i;
+ break;
+ }
+ }
+
+ NPNR_ASSERT(idx != -1);
+
+ dst_data.flags = WireInfo::RESERVED_ROUTE | unsigned(idx);
+}
+
+bool Arch::wires_connected(WireId src, WireId dst) const
+{
+ PipId pip(src.node, dst.node);
+ return getBoundPipNet(pip) != nullptr;
+}
+
+PipId Arch::add_pip(WireId src, WireId dst)
+{
+ wires[src].wires_downhill.push_back(dst);
+ wires[dst].wires_uphill.push_back(src);
+ return PipId(src.node, dst.node);
+}
+
+void Arch::add_bel_pin(BelId bel, IdString pin, PortType dir, WireId wire)
+{
+ auto &b = bel_data(bel);
+ NPNR_ASSERT(!b.pins.count(pin));
+ b.pins[pin].dir = dir;
+ b.pins[pin].wire = wire;
+
+ BelPin bel_pin;
+ bel_pin.bel = bel;
+ bel_pin.pin = pin;
+ wires[wire].bel_pins.push_back(bel_pin);
+}
+
+void Arch::assign_default_pinmap(CellInfo *cell)
+{
+ for (auto &port : cell->ports) {
+ auto &pinmap = cell->pin_data[port.first].bel_pins;
+ if (!pinmap.empty())
+ continue; // already mapped
+ if (is_comb_cell(cell->type) && comb_pinmap.count(port.first))
+ pinmap.push_back(comb_pinmap.at(port.first)); // default comb mapping for placer purposes
+ else
+ pinmap.push_back(port.first); // default: assume bel pin named the same as cell pin
+ }
+}
+
+void Arch::assignArchInfo()
+{
+ for (auto cell : sorted(cells)) {
+ CellInfo *ci = cell.second;
+ if (is_comb_cell(ci->type))
+ assign_comb_info(ci);
+ else if (ci->type == id_MISTRAL_FF)
+ assign_ff_info(ci);
+ assign_default_pinmap(ci);
+ }
+}
+
+delay_t Arch::estimateDelay(WireId src, WireId dst) const
+{
+ int x0 = CycloneV::rn2x(src.node);
+ int y0 = CycloneV::rn2y(src.node);
+ int x1 = CycloneV::rn2x(dst.node);
+ int y1 = CycloneV::rn2y(dst.node);
+ return 100 * std::abs(y1 - y0) + 100 * std::abs(x1 - x0) + 100;
+}
+
+ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const
+{
+ ArcBounds bounds;
+ int src_x = CycloneV::rn2x(src.node);
+ int src_y = CycloneV::rn2y(src.node);
+ int dst_x = CycloneV::rn2x(dst.node);
+ int dst_y = CycloneV::rn2y(dst.node);
+ bounds.x0 = std::min(src_x, dst_x);
+ bounds.y0 = std::min(src_y, dst_y);
+ bounds.x1 = std::max(src_x, dst_x);
+ bounds.y1 = std::max(src_y, dst_y);
+ return bounds;
+}
+
+delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
+{
+ if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId())
+ return 100;
+ if (sink.cell->bel == BelId())
+ return 100;
+ Loc src_loc = getBelLocation(net_info->driver.cell->bel);
+ Loc dst_loc = getBelLocation(sink.cell->bel);
+ return std::abs(dst_loc.y - src_loc.y) * 100 + std::abs(dst_loc.x - src_loc.x) * 100 + 100;
+}
+
+bool Arch::place()
+{
+ std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
+
+ if (placer == "heap") {
+ PlacerHeapCfg cfg(getCtx());
+ cfg.ioBufTypes.insert(id_MISTRAL_IO);
+ cfg.ioBufTypes.insert(id_MISTRAL_IB);
+ cfg.ioBufTypes.insert(id_MISTRAL_OB);
+ cfg.cellGroups.emplace_back();
+ cfg.cellGroups.back().insert({id_MISTRAL_COMB});
+ cfg.cellGroups.back().insert({id_MISTRAL_FF});
+
+ cfg.beta = 0.5; // TODO: find a good value of beta for sensible ALM spreading
+ cfg.criticalityExponent = 7;
+ if (!placer_heap(getCtx(), cfg))
+ return false;
+ } else if (placer == "sa") {
+ if (!placer1(getCtx(), Placer1Cfg(getCtx())))
+ return false;
+ } else {
+ log_error("Mistral architecture does not support placer '%s'\n", placer.c_str());
+ }
+
+ getCtx()->attrs[getCtx()->id("step")] = std::string("place");
+ archInfoToAttributes();
+ return true;
+}
+
+bool Arch::route()
+{
+ assign_budget(getCtx(), true);
+
+ lab_pre_route();
+
+ std::string router = str_or_default(settings, id("router"), defaultRouter);
+ bool result;
+ if (router == "router1") {
+ result = router1(getCtx(), Router1Cfg(getCtx()));
+ } else if (router == "router2") {
+ router2(getCtx(), Router2Cfg(getCtx()));
+ result = true;
+ } else {
+ log_error("Mistral architecture does not support router '%s'\n", router.c_str());
+ }
+ getCtx()->attrs[getCtx()->id("step")] = std::string("route");
+ archInfoToAttributes();
+ return result;
+}
+
+#ifdef WITH_HEAP
+const std::string Arch::defaultPlacer = "heap";
+#else
+const std::string Arch::defaultPlacer = "sa";
+#endif
+
+const std::vector<std::string> Arch::availablePlacers = {"sa",
+#ifdef WITH_HEAP
+ "heap"
+#endif
+};
+
+const std::string Arch::defaultRouter = "router2";
+const std::vector<std::string> Arch::availableRouters = {"router1", "router2"};
+
+NEXTPNR_NAMESPACE_END
diff --git a/mistral/arch.h b/mistral/arch.h
new file mode 100644
index 00000000..5913a615
--- /dev/null
+++ b/mistral/arch.h
@@ -0,0 +1,550 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 Lofty <dan.ravensloft@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.
+ *
+ */
+
+#ifndef MISTRAL_ARCH_H
+#define MISTRAL_ARCH_H
+
+#include <set>
+#include <sstream>
+
+#include "base_arch.h"
+#include "nextpnr_types.h"
+#include "relptr.h"
+
+#include "cyclonev.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+struct ArchArgs
+{
+ std::string device;
+ std::string mistral_root;
+};
+
+// These structures are used for fast ALM validity checking
+struct ALMInfo
+{
+ // Wires, so bitstream gen can determine connectivity
+ std::array<WireId, 2> comb_out;
+ std::array<WireId, 2> sel_clk, sel_ena, sel_aclr, sel_ef;
+ std::array<WireId, 4> ff_in, ff_out;
+ // Pointers to bels
+ std::array<BelId, 2> lut_bels;
+ std::array<BelId, 4> ff_bels;
+
+ bool l6_mode = false;
+
+ // Which CLK/ENA and ACLR is chosen for each half
+ std::array<int, 2> clk_ena_idx, aclr_idx;
+
+ // For keeping track of how many inputs are currently being used, for the LAB routeability check
+ int unique_input_count = 0;
+};
+
+struct LABInfo
+{
+ std::array<ALMInfo, 10> alms;
+ // Control set wires
+ std::array<WireId, 3> clk_wires, ena_wires;
+ std::array<WireId, 2> aclr_wires;
+ WireId sclr_wire, sload_wire;
+ // TODO: LAB configuration (control set etc)
+ std::array<bool, 2> aclr_used;
+};
+
+struct PinInfo
+{
+ WireId wire;
+ PortType dir;
+};
+
+struct BelInfo
+{
+ IdString name;
+ IdString type;
+ IdString bucket;
+
+ CellInfo *bound = nullptr;
+
+ // For cases where we need to determine an original block index, due to multiple bels at the same tile this
+ // might not be the same as the nextpnr z-coordinate
+ int block_index;
+ std::unordered_map<IdString, PinInfo> pins;
+ // Info for different kinds of bels
+ union
+ {
+ // This enables fast lookup of the associated ALM, etc
+ struct
+ {
+ uint32_t lab; // index into the list of LABs
+ uint8_t alm; // ALM index inside LAB
+ uint8_t idx; // LUT or FF index inside ALM
+ } lab_data;
+ };
+};
+
+// We maintain our own wire data based on mistral's. This gets us the bidirectional linking that nextpnr needs,
+// and also makes it easy to add wires and pips for our own purposes like LAB internal routing, global clock
+// sources, etc.
+struct WireInfo
+{
+ // name_override is only used for nextpnr-created wires
+ // otherwise; this is empty and a name is created according to mistral rules
+ IdString name_override;
+
+ // these are transformed on-the-fly to PipId by the iterator, to save space (WireId is half the size of PipId)
+ std::vector<WireId> wires_downhill;
+ std::vector<WireId> wires_uphill;
+
+ std::vector<BelPin> bel_pins;
+
+ // flags for special wires (currently unused)
+ uint64_t flags;
+
+ // if the RESERVED_ROUTE mask is set in flags, then only wires_uphill[flags&0xFF] may drive this wire - used for
+ // control set preallocations
+ static const uint64_t RESERVED_ROUTE = 0x100;
+};
+
+// This transforms a WireIds, and adds the mising half of the pair to create a PipId
+using WireVecIterator = std::vector<WireId>::const_iterator;
+struct UpDownhillPipIterator
+{
+ WireVecIterator base;
+ WireId other_wire;
+ bool is_uphill;
+
+ UpDownhillPipIterator(WireVecIterator base, WireId other_wire, bool is_uphill)
+ : base(base), other_wire(other_wire), is_uphill(is_uphill){};
+
+ bool operator!=(const UpDownhillPipIterator &other) { return base != other.base; }
+ UpDownhillPipIterator operator++()
+ {
+ ++base;
+ return *this;
+ }
+ UpDownhillPipIterator operator++(int)
+ {
+ UpDownhillPipIterator prior(*this);
+ ++(*this);
+ return prior;
+ }
+ PipId operator*() { return is_uphill ? PipId(base->node, other_wire.node) : PipId(other_wire.node, base->node); }
+};
+
+struct UpDownhillPipRange
+{
+ UpDownhillPipIterator b, e;
+
+ UpDownhillPipRange(const std::vector<WireId> &v, WireId other_wire, bool is_uphill)
+ : b(v.cbegin(), other_wire, is_uphill), e(v.cend(), other_wire, is_uphill){};
+
+ UpDownhillPipIterator begin() const { return b; }
+ UpDownhillPipIterator end() const { return e; }
+};
+
+// This iterates over the list of wires, and for each wire yields its uphill pips, as an efficient way of going over
+// all the pips in the device
+using WireMapIterator = std::unordered_map<WireId, WireInfo>::const_iterator;
+struct AllPipIterator
+{
+ WireMapIterator base, end;
+ int uphill_idx;
+
+ AllPipIterator(WireMapIterator base, WireMapIterator end, int uphill_idx)
+ : base(base), end(end), uphill_idx(uphill_idx){};
+
+ bool operator!=(const AllPipIterator &other) { return base != other.base || uphill_idx != other.uphill_idx; }
+ AllPipIterator operator++()
+ {
+ // Increment uphill list index by one
+ ++uphill_idx;
+ // We've reached the end of the current wire. Keep incrementing the wire of interest until we find one with
+ // uphill pips, or we reach the end of the list of wires
+ while (base != end && uphill_idx >= int(base->second.wires_uphill.size())) {
+ uphill_idx = 0;
+ ++base;
+ }
+ return *this;
+ }
+ AllPipIterator operator++(int)
+ {
+ AllPipIterator prior(*this);
+ ++(*this);
+ return prior;
+ }
+ PipId operator*() { return PipId(base->second.wires_uphill.at(uphill_idx).node, base->first.node); }
+};
+
+struct AllPipRange
+{
+ AllPipIterator b, e;
+
+ AllPipRange(const std::unordered_map<WireId, WireInfo> &wires)
+ : b(wires.cbegin(), wires.cend(), -1), e(wires.cend(), wires.cend(), 0)
+ {
+ // Starting the begin iterator at index -1 and incrementing it ensures we skip over the first wire if it has no
+ // uphill pips
+ ++b;
+ };
+
+ AllPipIterator begin() const { return b; }
+ AllPipIterator end() const { return e; }
+};
+
+// This transforms a map to a range of keys, used as the wire iterator
+template <typename T> struct key_range
+{
+ key_range(const T &t) : b(t.cbegin()), e(t.cend()){};
+ typename T::const_iterator b, e;
+
+ struct xformed_iterator : public T::const_iterator
+ {
+ explicit xformed_iterator(typename T::const_iterator base) : T::const_iterator(base){};
+ typename T::key_type operator*() { return this->T::const_iterator::operator*().first; }
+ };
+
+ xformed_iterator begin() const { return xformed_iterator(b); }
+ xformed_iterator end() const { return xformed_iterator(e); }
+};
+
+using AllWireRange = key_range<std::unordered_map<WireId, WireInfo>>;
+
+struct ArchRanges : BaseArchRanges
+{
+ using ArchArgsT = ArchArgs;
+ // Bels
+ using AllBelsRangeT = const std::vector<BelId> &;
+ using TileBelsRangeT = std::vector<BelId>;
+ using BelPinsRangeT = std::vector<IdString>;
+ using CellBelPinRangeT = const std::vector<IdString> &;
+ // Wires
+ using AllWiresRangeT = AllWireRange;
+ using DownhillPipRangeT = UpDownhillPipRange;
+ using UphillPipRangeT = UpDownhillPipRange;
+ using WireBelPinRangeT = const std::vector<BelPin> &;
+ // Pips
+ using AllPipsRangeT = AllPipRange;
+};
+
+// This enum captures different 'styles' of cell pins
+// This is a combination of the modes available for a pin (tied high, low or inverted)
+// and the default value to set it to not connected
+enum CellPinStyle
+{
+ PINOPT_NONE = 0x0, // no options, just signal as-is
+ PINOPT_LO = 0x1, // can be tied low
+ PINOPT_HI = 0x2, // can be tied high
+ PINOPT_INV = 0x4, // can be inverted
+
+ PINOPT_LOHI = 0x3, // can be tied low or high
+ PINOPT_LOHIINV = 0x7, // can be tied low or high; or inverted
+
+ PINOPT_MASK = 0x7,
+
+ PINDEF_NONE = 0x00, // leave disconnected
+ PINDEF_0 = 0x10, // connect to 0 if not used
+ PINDEF_1 = 0x20, // connect to 1 if not used
+
+ PINDEF_MASK = 0x30,
+
+ PINGLB_CLK = 0x100, // pin is a 'clock' for global purposes
+
+ PINGLB_MASK = 0x100,
+
+ PINSTYLE_NONE = 0x000, // default
+
+ PINSTYLE_COMB = 0x017, // combinational signal, defaults low, can be inverted and tied
+ PINSTYLE_CLK = 0x107, // CLK type signal, invertible and defaults to disconnected
+
+ PINSTYLE_CE = 0x027, // CE type signal, invertible and defaults to enabled
+ PINSTYLE_RST = 0x017, // RST type signal, invertible and defaults to not reset
+ PINSTYLE_DEDI = 0x000, // dedicated signals, leave alone
+ PINSTYLE_INP = 0x001, // general inputs, no inversion/tieing but defaults low
+ PINSTYLE_PU = 0x022, // signals that float high and default high
+
+ PINSTYLE_CARRY = 0x001, // carry chains can be floating or 0?
+
+};
+
+struct Arch : BaseArch<ArchRanges>
+{
+ ArchArgs args;
+ mistral::CycloneV *cyclonev;
+
+ Arch(ArchArgs args);
+ ArchArgs archArgs() const { return args; }
+
+ std::string getChipName() const override { return std::string{"TODO: getChipName"}; }
+ // -------------------------------------------------
+
+ int getGridDimX() const override { return cyclonev->get_tile_sx(); }
+ int getGridDimY() const override { return cyclonev->get_tile_sy(); }
+ int getTileBelDimZ(int x, int y) const override; // arch.cc
+ char getNameDelimiter() const override { return '.'; }
+
+ // -------------------------------------------------
+
+ BelId getBelByName(IdStringList name) const override; // arch.cc
+ IdStringList getBelName(BelId bel) const override; // arch.cc
+ const std::vector<BelId> &getBels() const override { return all_bels; }
+ std::vector<BelId> getBelsByTile(int x, int y) const override;
+ Loc getBelLocation(BelId bel) const override
+ {
+ return Loc(CycloneV::pos2x(bel.pos), CycloneV::pos2y(bel.pos), bel.z);
+ }
+ BelId getBelByLocation(Loc loc) const override
+ {
+ if (loc.x < 0 || loc.x >= cyclonev->get_tile_sx())
+ return BelId();
+ if (loc.y < 0 || loc.y >= cyclonev->get_tile_sy())
+ return BelId();
+ auto &bels = bels_by_tile.at(pos2idx(loc.x, loc.y));
+ if (loc.z < 0 || loc.z >= int(bels.size()))
+ return BelId();
+ return BelId(CycloneV::xy2pos(loc.x, loc.y), loc.z);
+ }
+ IdString getBelType(BelId bel) const override; // arch.cc
+ WireId getBelPinWire(BelId bel, IdString pin) const override
+ {
+ auto &pins = bel_data(bel).pins;
+ auto found = pins.find(pin);
+ if (found == pins.end())
+ return WireId();
+ else
+ return found->second.wire;
+ }
+ PortType getBelPinType(BelId bel, IdString pin) const override { return bel_data(bel).pins.at(pin).dir; }
+ std::vector<IdString> getBelPins(BelId bel) const override;
+
+ bool isBelLocationValid(BelId bel) const override;
+
+ void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) override
+ {
+ auto &data = bel_data(bel);
+ NPNR_ASSERT(data.bound == nullptr);
+ data.bound = cell;
+ cell->bel = bel;
+ cell->belStrength = strength;
+ update_bel(bel);
+ }
+ void unbindBel(BelId bel) override
+ {
+ auto &data = bel_data(bel);
+ NPNR_ASSERT(data.bound != nullptr);
+ data.bound->bel = BelId();
+ data.bound->belStrength = STRENGTH_NONE;
+ data.bound = nullptr;
+ update_bel(bel);
+ }
+ bool checkBelAvail(BelId bel) const override { return bel_data(bel).bound == nullptr; }
+ CellInfo *getBoundBelCell(BelId bel) const override { return bel_data(bel).bound; }
+ CellInfo *getConflictingBelCell(BelId bel) const override { return bel_data(bel).bound; }
+
+ void update_bel(BelId bel);
+ BelId bel_by_block_idx(int x, int y, IdString type, int block_index) const;
+
+ // -------------------------------------------------
+
+ WireId getWireByName(IdStringList name) const override;
+ IdStringList getWireName(WireId wire) const override;
+ DelayQuad getWireDelay(WireId wire) const override { return DelayQuad(0); }
+ const std::vector<BelPin> &getWireBelPins(WireId wire) const override { return wires.at(wire).bel_pins; }
+ AllWireRange getWires() const override { return AllWireRange(wires); }
+
+ bool wires_connected(WireId src, WireId dst) const;
+ // Only allow src, and not any other wire, to drive dst
+ void reserve_route(WireId src, WireId dst);
+
+ // -------------------------------------------------
+
+ PipId getPipByName(IdStringList name) const override;
+ AllPipRange getPips() const override { return AllPipRange(wires); }
+ Loc getPipLocation(PipId pip) const override { return Loc(CycloneV::rn2x(pip.dst), CycloneV::rn2y(pip.dst), 0); }
+ IdStringList getPipName(PipId pip) const override;
+ WireId getPipSrcWire(PipId pip) const override { return WireId(pip.src); };
+ WireId getPipDstWire(PipId pip) const override { return WireId(pip.dst); };
+ DelayQuad getPipDelay(PipId pip) const override { return DelayQuad(100); }
+ UpDownhillPipRange getPipsDownhill(WireId wire) const override
+ {
+ return UpDownhillPipRange(wires.at(wire).wires_downhill, wire, false);
+ }
+ UpDownhillPipRange getPipsUphill(WireId wire) const override
+ {
+ return UpDownhillPipRange(wires.at(wire).wires_uphill, wire, true);
+ }
+
+ bool checkPipAvail(PipId pip) const override
+ {
+ // Check reserved routes
+ WireId dst(pip.dst);
+ const auto &dst_data = wires.at(dst);
+ if ((dst_data.flags & WireInfo::RESERVED_ROUTE) != 0) {
+ if (WireId(pip.src) != dst_data.wires_uphill.at(dst_data.flags & 0xFF))
+ return false;
+ }
+ return BaseArch::checkPipAvail(pip);
+ }
+
+ bool checkPipAvailForNet(PipId pip, NetInfo *net) const override
+ {
+ if (!checkPipAvail(pip))
+ return false;
+ return BaseArch::checkPipAvailForNet(pip, net);
+ }
+
+ // -------------------------------------------------
+
+ delay_t estimateDelay(WireId src, WireId dst) const override;
+ delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override;
+ delay_t getDelayEpsilon() const override { return 10; };
+ delay_t getRipupDelayPenalty() const override { return 100; };
+ float getDelayNS(delay_t v) const override { return float(v) / 1000.0f; };
+ delay_t getDelayFromNS(float ns) const override { return delay_t(ns * 1000.0f); };
+ uint32_t getDelayChecksum(delay_t v) const override { return v; };
+
+ ArcBounds getRouteBoundingBox(WireId src, WireId dst) const override;
+
+ // -------------------------------------------------
+
+ const std::vector<IdString> &getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const override
+ {
+ return cell_info->pin_data.at(pin).bel_pins;
+ }
+
+ bool isValidBelForCellType(IdString cell_type, BelId bel) const override;
+ BelBucketId getBelBucketForCellType(IdString cell_type) const override;
+
+ // -------------------------------------------------
+
+ void assignArchInfo() override;
+ bool pack() override;
+ bool place() override;
+ bool route() override;
+
+ // -------------------------------------------------
+ // Functions for device setup
+
+ BelId add_bel(int x, int y, IdString name, IdString type);
+ WireId add_wire(int x, int y, IdString name, uint64_t flags = 0);
+ PipId add_pip(WireId src, WireId dst);
+
+ void add_bel_pin(BelId bel, IdString pin, PortType dir, WireId wire);
+
+ WireId get_port(CycloneV::block_type_t bt, int x, int y, int bi, CycloneV::port_type_t port, int pi = -1) const
+ {
+ return WireId(cyclonev->pnode_to_rnode(CycloneV::pnode(bt, x, y, port, bi, pi)));
+ }
+
+ void create_lab(int x, int y); // lab.cc
+ void create_gpio(int x, int y); // io.cc
+ void create_clkbuf(int x, int y); // globals.cc
+
+ // -------------------------------------------------
+
+ bool is_comb_cell(IdString cell_type) const; // lab.cc
+ bool is_alm_legal(uint32_t lab, uint8_t alm) const; // lab.cc
+ bool is_lab_ctrlset_legal(uint32_t lab) const; // lab.cc
+ bool check_lab_input_count(uint32_t lab) const; // lab.cc
+
+ void assign_comb_info(CellInfo *cell) const; // lab.cc
+ void assign_ff_info(CellInfo *cell) const; // lab.cc
+
+ void lab_pre_route(); // lab.cc
+ void assign_control_sets(uint32_t lab); // lab.cc
+ void reassign_alm_inputs(uint32_t lab, uint8_t alm); // lab.cc
+ void update_alm_input_count(uint32_t lab, uint8_t alm); // lab.cc
+
+ uint64_t compute_lut_mask(uint32_t lab, uint8_t alm); // lab.cc
+
+ // -------------------------------------------------
+
+ bool is_io_cell(IdString cell_type) const; // io.cc
+ BelId get_io_pin_bel(const CycloneV::pin_info_t *pin) const; // io.cc
+
+ // -------------------------------------------------
+
+ bool is_clkbuf_cell(IdString cell_type) const; // globals.cc
+
+ // -------------------------------------------------
+
+ static const std::string defaultPlacer;
+ static const std::vector<std::string> availablePlacers;
+ static const std::string defaultRouter;
+ static const std::vector<std::string> availableRouters;
+
+ std::unordered_map<WireId, WireInfo> wires;
+
+ // List of LABs
+ std::vector<LABInfo> labs;
+
+ // WIP to link without failure
+ std::vector<BelPin> empty_belpin_list;
+
+ // Conversion between numbers and rnode types and IdString, for fast wire name implementation
+ std::vector<IdString> int2id;
+ std::unordered_map<IdString, int> id2int;
+
+ std::vector<IdString> rn_t2id;
+ std::unordered_map<IdString, CycloneV::rnode_type_t> id2rn_t;
+
+ // This structure is only used for nextpnr-created wires
+ std::unordered_map<IdStringList, WireId> npnr_wirebyname;
+
+ std::vector<std::vector<BelInfo>> bels_by_tile;
+ std::vector<BelId> all_bels;
+
+ size_t pos2idx(int x, int y) const
+ {
+ NPNR_ASSERT(x >= 0 && x < int(cyclonev->get_tile_sx()));
+ NPNR_ASSERT(y >= 0 && y < int(cyclonev->get_tile_sy()));
+ return y * cyclonev->get_tile_sx() + x;
+ }
+
+ size_t pos2idx(CycloneV::pos_t pos) const { return pos2idx(CycloneV::pos2x(pos), CycloneV::pos2y(pos)); }
+
+ BelInfo &bel_data(BelId bel) { return bels_by_tile.at(pos2idx(bel.pos)).at(bel.z); }
+ const BelInfo &bel_data(BelId bel) const { return bels_by_tile.at(pos2idx(bel.pos)).at(bel.z); }
+
+ // -------------------------------------------------
+
+ void assign_default_pinmap(CellInfo *cell);
+ static const std::unordered_map<IdString, IdString> comb_pinmap;
+
+ // -------------------------------------------------
+
+ typedef std::unordered_map<IdString, CellPinStyle> CellPinsData; // pins.cc
+ static const std::unordered_map<IdString, CellPinsData> cell_pins_db; // pins.cc
+ CellPinStyle get_cell_pin_style(const CellInfo *cell, IdString port) const; // pins.cc
+
+ // -------------------------------------------------
+
+ // List of IO constraints, used by QSF parser
+ std::unordered_map<IdString, std::unordered_map<IdString, Property>> io_attr;
+ void read_qsf(std::istream &in); // qsf.cc
+
+ // -------------------------------------------------
+
+ void init_base_bitstream(); // base_bitstream.cc
+ void build_bitstream(); // bitstream.cc
+};
+
+NEXTPNR_NAMESPACE_END
+
+#endif
diff --git a/mistral/arch_pybindings.cc b/mistral/arch_pybindings.cc
new file mode 100644
index 00000000..23716c93
--- /dev/null
+++ b/mistral/arch_pybindings.cc
@@ -0,0 +1,87 @@
+
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Claire Xen <claire@symbioticeda.com>
+ * Copyright (C) 2018 gatecat <gatecat@ds0.me>
+ *
+ * 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.
+ *
+ */
+
+#ifndef NO_PYTHON
+
+#include "arch_pybindings.h"
+#include "nextpnr.h"
+#include "pybindings.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void arch_wrap_python(py::module &m)
+{
+ using namespace PythonConversion;
+ py::class_<ArchArgs>(m, "ArchArgs").def_readwrite("device", &ArchArgs::device);
+
+ py::class_<BelId>(m, "BelId").def_readwrite("pos", &BelId::pos).def_readwrite("z", &BelId::z);
+
+ py::class_<WireId>(m, "WireId").def_readwrite("node", &WireId::node);
+
+ py::class_<PipId>(m, "PipId").def_readwrite("src", &PipId::src).def_readwrite("dst", &PipId::dst);
+
+ auto arch_cls = py::class_<Arch, BaseCtx>(m, "Arch").def(py::init<ArchArgs>());
+ auto ctx_cls = py::class_<Context, Arch>(m, "Context")
+ .def("checksum", &Context::checksum)
+ .def("pack", &Context::pack)
+ .def("place", &Context::place)
+ .def("route", &Context::route);
+
+ fn_wrapper_2a<Context, decltype(&Context::wires_connected), &Context::wires_connected, pass_through<bool>,
+ conv_from_str<WireId>, conv_from_str<WireId>>::def_wrap(ctx_cls, "wires_connected");
+ fn_wrapper_2a<Context, decltype(&Context::compute_lut_mask), &Context::compute_lut_mask, pass_through<uint64_t>,
+ pass_through<uint32_t>, pass_through<uint8_t>>::def_wrap(ctx_cls, "compute_lut_mask");
+
+ typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap;
+ typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap;
+ typedef std::unordered_map<IdString, IdString> AliasMap;
+ typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap;
+
+ auto belpin_cls = py::class_<ContextualWrapper<BelPin>>(m, "BelPin");
+ readonly_wrapper<BelPin, decltype(&BelPin::bel), &BelPin::bel, conv_to_str<BelId>>::def_wrap(belpin_cls, "bel");
+ readonly_wrapper<BelPin, decltype(&BelPin::pin), &BelPin::pin, conv_to_str<IdString>>::def_wrap(belpin_cls, "pin");
+
+ typedef const std::vector<BelId> &BelRange;
+
+ typedef UpDownhillPipRange UphillPipRange;
+ typedef UpDownhillPipRange DownhillPipRange;
+
+ typedef AllWireRange WireRange;
+ typedef const std::vector<BelPin> &BelPinRange;
+
+ typedef const std::vector<BelBucketId> &BelBucketRange;
+ typedef const std::vector<BelId> &BelRangeForBelBucket;
+#include "arch_pybindings_shared.h"
+
+ WRAP_RANGE(m, Bel, conv_to_str<BelId>);
+ WRAP_RANGE(m, AllWire, conv_to_str<WireId>);
+ WRAP_RANGE(m, AllPip, conv_to_str<PipId>);
+ WRAP_RANGE(m, UpDownhillPip, conv_to_str<PipId>);
+ WRAP_RANGE(m, BelPin, wrap_context<BelPin>);
+
+ WRAP_MAP_UPTR(m, CellMap, "IdCellMap");
+ WRAP_MAP_UPTR(m, NetMap, "IdNetMap");
+ WRAP_MAP(m, HierarchyMap, wrap_context<HierarchicalCell &>, "HierarchyMap");
+}
+
+NEXTPNR_NAMESPACE_END
+
+#endif // NO_PYTHON
diff --git a/mistral/arch_pybindings.h b/mistral/arch_pybindings.h
new file mode 100644
index 00000000..aa5b322e
--- /dev/null
+++ b/mistral/arch_pybindings.h
@@ -0,0 +1,125 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Claire Xen <claire@symbioticeda.com>
+ * Copyright (C) 2018 gatecat <gatecat@ds0.me>
+ *
+ * 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.
+ *
+ */
+#ifndef ARCH_PYBINDINGS_H
+#define ARCH_PYBINDINGS_H
+#ifndef NO_PYTHON
+
+#include "nextpnr.h"
+#include "pybindings.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace PythonConversion {
+
+template <> struct string_converter<BelId>
+{
+ BelId from_str(Context *ctx, std::string name) { return ctx->getBelByNameStr(name); }
+
+ std::string to_str(Context *ctx, BelId id)
+ {
+ if (id == BelId())
+ throw bad_wrap();
+ return ctx->getBelName(id).str(ctx);
+ }
+};
+
+template <> struct string_converter<const BelId &>
+{
+ const BelId &from_str(Context *ctx, std::string name) { NPNR_ASSERT_FALSE("unreachable"); }
+
+ std::string to_str(Context *ctx, const BelId &id)
+ {
+ if (id == BelId())
+ throw bad_wrap();
+ return ctx->getBelName(id).str(ctx);
+ }
+};
+
+template <> struct string_converter<WireId>
+{
+ WireId from_str(Context *ctx, std::string name) { return ctx->getWireByNameStr(name); }
+
+ std::string to_str(Context *ctx, WireId id)
+ {
+ if (id == WireId())
+ throw bad_wrap();
+ return ctx->getWireName(id).str(ctx);
+ }
+};
+
+template <> struct string_converter<const WireId>
+{
+ WireId from_str(Context *ctx, std::string name) { return ctx->getWireByNameStr(name); }
+
+ std::string to_str(Context *ctx, WireId id)
+ {
+ if (id == WireId())
+ throw bad_wrap();
+ return ctx->getWireName(id).str(ctx);
+ }
+};
+
+template <> struct string_converter<PipId>
+{
+ PipId from_str(Context *ctx, std::string name) { return ctx->getPipByNameStr(name); }
+
+ std::string to_str(Context *ctx, PipId id)
+ {
+ if (id == PipId())
+ throw bad_wrap();
+ return ctx->getPipName(id).str(ctx);
+ }
+};
+
+template <> struct string_converter<BelPin>
+{
+ BelPin from_str(Context *ctx, std::string name)
+ {
+ NPNR_ASSERT_FALSE("string_converter<BelPin>::from_str not implemented");
+ }
+
+ std::string to_str(Context *ctx, BelPin pin)
+ {
+ if (pin.bel == BelId())
+ throw bad_wrap();
+ return ctx->getBelName(pin.bel).str(ctx) + "/" + pin.pin.str(ctx);
+ }
+};
+
+template <> struct string_converter<const BelPin &>
+{
+ BelPin from_str(Context *ctx, std::string name)
+ {
+ NPNR_ASSERT_FALSE("string_converter<BelPin>::from_str not implemented");
+ }
+
+ std::string to_str(Context *ctx, const BelPin &pin)
+ {
+ if (pin.bel == BelId())
+ throw bad_wrap();
+ return ctx->getBelName(pin.bel).str(ctx) + "/" + pin.pin.str(ctx);
+ }
+};
+
+} // namespace PythonConversion
+
+NEXTPNR_NAMESPACE_END
+#endif
+#endif
diff --git a/mistral/archdefs.cc b/mistral/archdefs.cc
new file mode 100644
index 00000000..8a2bbe7a
--- /dev/null
+++ b/mistral/archdefs.cc
@@ -0,0 +1,33 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 gatecat <gatecat@ds0.me>
+ *
+ * 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"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+CellPinState ArchCellInfo::get_pin_state(IdString pin) const
+{
+ auto fnd = pin_data.find(pin);
+ if (fnd != pin_data.end())
+ return fnd->second.state;
+ else
+ return PIN_SIG;
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/mistral/archdefs.h b/mistral/archdefs.h
new file mode 100644
index 00000000..5fd02ad5
--- /dev/null
+++ b/mistral/archdefs.h
@@ -0,0 +1,236 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 Lofty <dan.ravensloft@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.
+ *
+ */
+
+#ifndef MISTRAL_ARCHDEFS_H
+#define MISTRAL_ARCHDEFS_H
+
+#include <boost/functional/hash.hpp>
+
+#include "base_clusterinfo.h"
+#include "cyclonev.h"
+
+#include "idstring.h"
+#include "nextpnr_assertions.h"
+#include "nextpnr_namespaces.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+using mistral::CycloneV;
+
+typedef int delay_t;
+
+// https://bugreports.qt.io/browse/QTBUG-80789
+
+#ifndef Q_MOC_RUN
+enum ConstIds
+{
+ ID_NONE
+#define X(t) , ID_##t
+#include "constids.inc"
+#undef X
+};
+
+#define X(t) static constexpr auto id_##t = IdString(ID_##t);
+#include "constids.inc"
+#undef X
+#endif
+
+struct DelayInfo
+{
+ delay_t delay = 0;
+
+ delay_t minRaiseDelay() const { return delay; }
+ delay_t maxRaiseDelay() const { return delay; }
+
+ delay_t minFallDelay() const { return delay; }
+ delay_t maxFallDelay() const { return delay; }
+
+ delay_t minDelay() const { return delay; }
+ delay_t maxDelay() const { return delay; }
+
+ DelayInfo operator+(const DelayInfo &other) const
+ {
+ DelayInfo ret;
+ ret.delay = this->delay + other.delay;
+ return ret;
+ }
+};
+
+struct BelId
+{
+ BelId() = default;
+ BelId(CycloneV::pos_t _pos, uint16_t _z) : pos{_pos}, z{_z} {}
+
+ // pos_t is used for X/Y, nextpnr-cyclonev uses its own Z coordinate system.
+ CycloneV::pos_t pos = 0;
+ uint16_t z = 0;
+
+ bool operator==(const BelId &other) const { return pos == other.pos && z == other.z; }
+ bool operator!=(const BelId &other) const { return pos != other.pos || z != other.z; }
+ bool operator<(const BelId &other) const { return pos < other.pos || (pos == other.pos && z < other.z); }
+};
+
+static constexpr auto invalid_rnode = std::numeric_limits<CycloneV::rnode_t>::max();
+
+struct WireId
+{
+ WireId() = default;
+ explicit WireId(CycloneV::rnode_t node) : node(node){};
+ CycloneV::rnode_t node = invalid_rnode;
+
+ // Wires created by nextpnr have rnode type >= 128
+ bool is_nextpnr_created() const
+ {
+ NPNR_ASSERT(node != invalid_rnode);
+ return CycloneV::rn2t(node) >= 128;
+ }
+
+ bool operator==(const WireId &other) const { return node == other.node; }
+ bool operator!=(const WireId &other) const { return node != other.node; }
+ bool operator<(const WireId &other) const { return node < other.node; }
+};
+
+struct PipId
+{
+ PipId() = default;
+ PipId(CycloneV::rnode_t src, CycloneV::rnode_t dst) : src(src), dst(dst){};
+ CycloneV::rnode_t src = invalid_rnode, dst = invalid_rnode;
+
+ bool operator==(const PipId &other) const { return src == other.src && dst == other.dst; }
+ bool operator!=(const PipId &other) const { return src != other.src || dst != other.dst; }
+ bool operator<(const PipId &other) const { return dst < other.dst || (dst == other.dst && src < other.src); }
+};
+
+typedef IdString DecalId;
+typedef IdString GroupId;
+typedef IdString BelBucketId;
+typedef IdString ClusterId;
+
+struct ArchNetInfo
+{
+ bool is_global = false;
+};
+
+enum CellPinState
+{
+ PIN_SIG = 0,
+ PIN_0 = 1,
+ PIN_1 = 2,
+ PIN_INV = 3,
+};
+
+struct ArchPinInfo
+{
+ // Used to represent signals that are either tied to implicit constants (rather than explicitly routed constants);
+ // or are inverted
+ CellPinState state = PIN_SIG;
+ // The physical bel pins that this logical pin maps to
+ std::vector<IdString> bel_pins;
+};
+
+struct NetInfo;
+
+// Structures for representing how FF control sets are stored and validity-checked
+struct ControlSig
+{
+ const NetInfo *net;
+ bool inverted;
+
+ bool connected() const { return net != nullptr; }
+ bool operator==(const ControlSig &other) const { return net == other.net && inverted == other.inverted; }
+ bool operator!=(const ControlSig &other) const { return net == other.net && inverted == other.inverted; }
+};
+
+struct FFControlSet
+{
+ ControlSig clk, ena, aclr, sclr, sload;
+
+ bool operator==(const FFControlSet &other) const
+ {
+ return clk == other.clk && ena == other.ena && aclr == other.aclr && sclr == other.sclr && sload == other.sload;
+ }
+ bool operator!=(const FFControlSet &other) const { return !(*this == other); }
+};
+
+struct ArchCellInfo : BaseClusterInfo
+{
+ union
+ {
+ struct
+ {
+ // Store the nets here for fast validity checking (avoids too many map lookups in a hot path)
+ std::array<const NetInfo *, 7> lut_in;
+ const NetInfo *comb_out;
+
+ int lut_input_count;
+ int used_lut_input_count; // excluding those null/constant
+ int lut_bits_count;
+
+ // for the LAB routeability check (see the detailed description in lab.cc); usually the same signal feeding
+ // multiple ALMs in a LAB is counted multiple times, due to not knowing which routing resources it will need
+ // in each case. But carry chains where we know how things will pack are allowed to share across ALMs as a
+ // special case, primarily to support adders/subtractors with a 'B invert' control signal shared across all
+ // ALMs.
+ int chain_shared_input_count;
+
+ bool is_carry, is_shared, is_extended;
+ bool carry_start, carry_end;
+ } combInfo;
+ struct
+ {
+ FFControlSet ctrlset;
+ const NetInfo *sdata, *datain;
+ } ffInfo;
+ };
+
+ std::unordered_map<IdString, ArchPinInfo> pin_data;
+
+ CellPinState get_pin_state(IdString pin) const;
+};
+
+NEXTPNR_NAMESPACE_END
+
+namespace std {
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX BelId>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept
+ {
+ return hash<uint32_t>()((static_cast<uint32_t>(bel.pos) << 16) | bel.z);
+ }
+};
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX WireId>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept
+ {
+ return hash<uint32_t>()(wire.node);
+ }
+};
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX PipId>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept
+ {
+ return hash<uint64_t>()((uint64_t(pip.dst) << 32) | pip.src);
+ }
+};
+
+} // namespace std
+
+#endif
diff --git a/mistral/base_bitstream.cc b/mistral/base_bitstream.cc
new file mode 100644
index 00000000..9fa74fb9
--- /dev/null
+++ b/mistral/base_bitstream.cc
@@ -0,0 +1,100 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 gatecat <gatecat@ds0.me>
+ *
+ * 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 "log.h"
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace {
+// Device-specific default config for the sx120f die
+void default_sx120f(CycloneV *cv)
+{
+ // Default PMA config?
+ cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 11), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::TRISTATE);
+ cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 11), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::TRISTATE);
+ cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 23), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::DOWN);
+ cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 23), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::UP);
+ cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::UP);
+ cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::UP);
+ cv->bmux_b_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FPLL_DRV_EN, 0, 0);
+ cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::HCLK_TOP_OUT_DRIVER, 0, CycloneV::TRISTATE);
+ // Default PLL config
+ cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN0, 0, 1);
+ cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN0_PRECOMP, 0, 1);
+ cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN1, 0, 1);
+ cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN1_PRECOMP, 0, 1);
+ cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_BG_KICKSTART, 0, 1);
+ cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_VBGMON_POWERDOWN, 0, 1);
+ // Default TERM config
+ cv->bmux_b_set(CycloneV::TERM, CycloneV::xy2pos(89, 34), CycloneV::INTOSC_2_EN, 0, 0);
+
+ // TODO: what if these pins are used? where do these come from
+ for (int z = 0; z < 4; z++) {
+ cv->bmux_m_set(CycloneV::GPIO, CycloneV::xy2pos(89, 43), CycloneV::IOCSR_STD, z, CycloneV::NVR_LOW);
+ cv->bmux_m_set(CycloneV::GPIO, CycloneV::xy2pos(89, 66), CycloneV::IOCSR_STD, z, CycloneV::NVR_LOW);
+ }
+ for (int y : {38, 44, 51, 58, 65, 73, 79}) {
+ // TODO: Why only these upper DQS? is there a pattern?
+ cv->bmux_b_set(CycloneV::DQS16, CycloneV::xy2pos(89, y), CycloneV::RB_2X_CLK_DQS_INV, 0, 1);
+ cv->bmux_b_set(CycloneV::DQS16, CycloneV::xy2pos(89, y), CycloneV::RB_ACLR_LFIFO_EN, 0, 1);
+ cv->bmux_b_set(CycloneV::DQS16, CycloneV::xy2pos(89, y), CycloneV::RB_LFIFO_BYPASS, 0, 0);
+ }
+
+ // Discover these mux values using
+ // grep 'i [_A-Z0-9.]* 1' empty.bt
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 12), 69), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 13), 4), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 34), 69), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 35), 4), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 37), 31), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 40), 43), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 46), 69), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 47), 43), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 53), 69), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 54), 4), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 73), 68), true);
+
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 18), 66), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 20), 8), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 27), 69), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 28), 43), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 59), 66), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 61), 8), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 68), 69), true);
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 69), 43), true);
+
+ for (int z = 10; z <= 45; z++)
+ cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(51, 80), z), true);
+}
+} // namespace
+
+void Arch::init_base_bitstream()
+{
+ switch (cyclonev->current_model()->variant.die.type) {
+ case CycloneV::SX120F:
+ default_sx120f(cyclonev);
+ break;
+ default:
+ log_error("FIXME: die type %s currently unsupported for bitgen.\n",
+ cyclonev->current_model()->variant.die.name);
+ }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc
new file mode 100644
index 00000000..92d86410
--- /dev/null
+++ b/mistral/bitstream.cc
@@ -0,0 +1,361 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 gatecat <gatecat@ds0.me>
+ *
+ * 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 "log.h"
+#include "nextpnr.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+namespace {
+struct MistralBitgen
+{
+ MistralBitgen(Context *ctx) : ctx(ctx), cv(ctx->cyclonev){};
+ Context *ctx;
+ CycloneV *cv;
+
+ void init()
+ {
+ ctx->init_base_bitstream();
+ // Default options
+ cv->opt_b_set(CycloneV::ALLOW_DEVICE_WIDE_OUTPUT_ENABLE_DIS, true);
+ cv->opt_n_set(CycloneV::CRC_DIVIDE_ORDER, 8);
+ cv->opt_b_set(CycloneV::CVP_CONF_DONE_EN, true);
+ cv->opt_b_set(CycloneV::DEVICE_WIDE_RESET_EN, true);
+ cv->opt_n_set(CycloneV::DRIVE_STRENGTH, 8);
+ cv->opt_b_set(CycloneV::IOCSR_READY_FROM_CSR_DONE_EN, true);
+ cv->opt_b_set(CycloneV::NCEO_DIS, true);
+ cv->opt_b_set(CycloneV::OCT_DONE_DIS, true);
+ cv->opt_r_set(CycloneV::OPT_A, 0x1dff);
+ cv->opt_r_set(CycloneV::OPT_B, 0xffffff402dffffffULL);
+ cv->opt_b_set(CycloneV::RELEASE_CLEARS_BEFORE_TRISTATES_DIS, true);
+ cv->opt_b_set(CycloneV::RETRY_CONFIG_ON_ERROR_EN, true);
+ cv->opt_r_set(CycloneV::START_UP_CLOCK, 0x3F);
+ // Default inversion
+ write_default_inv();
+ }
+
+ void write_default_inv()
+ {
+ // Some PNODEs are inverted by default. Set them up here.
+ for (const auto &pn2r : cv->get_all_p2r()) {
+ const auto &pn = pn2r.first;
+ auto pt = CycloneV::pn2pt(pn);
+ auto pi = CycloneV::pn2pi(pn);
+
+ switch (CycloneV::pn2bt(pn)) {
+ case CycloneV::HMC: {
+ // HMC OE are inverted to set OE=0, i.e. unused pins floating
+ // TODO: handle the case when we are using the HMC or HMC bypass
+ std::string name(CycloneV::port_type_names[pt]);
+ if (name.compare(0, 5, "IOINT") != 0 || name.compare(name.size() - 2, 2, "OE") != 0)
+ continue;
+ cv->inv_set(pn2r.second, true);
+ break;
+ };
+ // HPS IO - TODO: what about when we actually support the HPS primitives?
+ case CycloneV::HPS_BOOT: {
+ switch (pt) {
+ case CycloneV::CSEL_EN:
+ case CycloneV::BSEL_EN:
+ case CycloneV::BOOT_FROM_FPGA_READY:
+ case CycloneV::BOOT_FROM_FPGA_ON_FAILURE:
+ cv->inv_set(pn2r.second, true);
+ break;
+ case CycloneV::CSEL:
+ if (pi < 2)
+ cv->inv_set(pn2r.second, true);
+ break;
+ case CycloneV::BSEL:
+ if (pi < 3)
+ cv->inv_set(pn2r.second, true);
+ break;
+ default:
+ break;
+ };
+ break;
+ };
+ case CycloneV::HPS_CROSS_TRIGGER: {
+ if (pt == CycloneV::CLK_EN)
+ cv->inv_set(pn2r.second, true);
+ break;
+ };
+ case CycloneV::HPS_TEST: {
+ if (pt == CycloneV::CFG_DFX_BYPASS_ENABLE)
+ cv->inv_set(pn2r.second, true);
+ break;
+ };
+ case CycloneV::GPIO: {
+ // Ignore GPIO used by the design
+ BelId bel = ctx->bel_by_block_idx(CycloneV::pn2x(pn), CycloneV::pn2y(pn), id_MISTRAL_IO,
+ CycloneV::pn2bi(pn));
+ if (bel != BelId() && ctx->getBoundBelCell(bel) != nullptr)
+ continue;
+ // Bonded IO invert OEIN.1 which disables the output buffer and floats the IO
+ // Unbonded IO invert OEIN.0 which enables the output buffer, and {DATAIN.[0123]} to drive a constant
+ // GND, presumably for power/EMI reasons
+ bool is_bonded = cv->pin_find_pnode(pn) != nullptr;
+ if (is_bonded && (pt != CycloneV::OEIN || pi != 1))
+ continue;
+ if (!is_bonded && (pt != CycloneV::DATAIN) && (pt != CycloneV::OEIN || pi != 0))
+ continue;
+ cv->inv_set(pn2r.second, true);
+ break;
+ };
+ case CycloneV::FPLL: {
+ if (pt == CycloneV::EXTSWITCH || (pt == CycloneV::CLKEN && pi < 2))
+ cv->inv_set(pn2r.second, true);
+ break;
+ };
+ default:
+ break;
+ }
+ }
+ }
+
+ void write_dqs()
+ {
+ for (auto pos : cv->dqs16_get_pos()) {
+ int x = CycloneV::pos2x(pos), y = CycloneV::pos2y(pos);
+ // DQS bypass for used output pins
+ for (int z = 0; z < 16; z++) {
+ int ioy = y + (z / 4) - 2;
+ if (ioy < 0 || ioy >= int(cv->get_tile_sy()))
+ continue;
+ BelId bel = ctx->bel_by_block_idx(x, ioy, id_MISTRAL_IO, z % 4);
+ if (bel == BelId())
+ continue;
+ CellInfo *ci = ctx->getBoundBelCell(bel);
+ if (ci == nullptr || (ci->type != id_MISTRAL_IO && ci->type != id_MISTRAL_OB))
+ continue; // not an output
+ cv->bmux_m_set(CycloneV::DQS16, pos, CycloneV::INPUT_REG4_SEL, z, CycloneV::SEL_LOCKED_DPA);
+ cv->bmux_r_set(CycloneV::DQS16, pos, CycloneV::RB_T9_SEL_EREG_CFF_DELAY, z, 0x1f);
+ }
+ }
+ }
+
+ void write_routing()
+ {
+ for (auto net : sorted(ctx->nets)) {
+ NetInfo *ni = net.second;
+ for (auto wire : sorted_ref(ni->wires)) {
+ PipId pip = wire.second.pip;
+ if (pip == PipId())
+ continue;
+ WireId src = ctx->getPipSrcWire(pip), dst = ctx->getPipDstWire(pip);
+ // Only write out routes that are entirely in the Mistral domain. Everything else is dealt with
+ // specially
+ if (src.is_nextpnr_created() || dst.is_nextpnr_created())
+ continue;
+ cv->rnode_link(src.node, dst.node);
+ }
+ }
+ }
+
+ void write_io_cell(CellInfo *ci, int x, int y, int bi)
+ {
+ bool is_output =
+ (ci->type == id_MISTRAL_OB || (ci->type == id_MISTRAL_IO && get_net_or_empty(ci, id_OE) != nullptr));
+ auto pos = CycloneV::xy2pos(x, y);
+ // TODO: configurable pull, IO standard, etc
+ cv->bmux_b_set(CycloneV::GPIO, pos, CycloneV::USE_WEAK_PULLUP, bi, false);
+ if (is_output) {
+ cv->bmux_m_set(CycloneV::GPIO, pos, CycloneV::DRIVE_STRENGTH, bi, CycloneV::V3P3_LVTTL_16MA_LVCMOS_2MA);
+ cv->bmux_m_set(CycloneV::GPIO, pos, CycloneV::IOCSR_STD, bi, CycloneV::DIS);
+ }
+ // There seem to be two mirrored OEIN inversion bits for constant OE for inputs/outputs. This might be to
+ // prevent a single bitflip from turning inputs to outputs and messing up other devices on the boards, notably
+ // ECP5 does similar. OEIN.0 inverted for outputs; OEIN.1 for inputs
+ cv->inv_set(cv->pnode_to_rnode(CycloneV::pnode(CycloneV::GPIO, pos, CycloneV::OEIN, bi, is_output ? 0 : 1)),
+ true);
+ }
+
+ void write_clkbuf_cell(CellInfo *ci, int x, int y, int bi)
+ {
+ (void)ci; // currently unused
+ auto pos = CycloneV::xy2pos(x, y);
+ cv->bmux_r_set(CycloneV::CMUXHG, pos, CycloneV::INPUT_SELECT, bi, 0x1b); // hardcode to general routing
+ cv->bmux_m_set(CycloneV::CMUXHG, pos, CycloneV::TESTSYN_ENOUT_SELECT, bi, CycloneV::PRE_SYNENB);
+ }
+
+ void write_cells()
+ {
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ Loc loc = ctx->getBelLocation(ci->bel);
+ int bi = ctx->bel_data(ci->bel).block_index;
+ if (ctx->is_io_cell(ci->type))
+ write_io_cell(ci, loc.x, loc.y, bi);
+ else if (ctx->is_clkbuf_cell(ci->type))
+ write_clkbuf_cell(ci, loc.x, loc.y, bi);
+ }
+ }
+
+ bool write_alm(uint32_t lab, uint8_t alm)
+ {
+ auto &alm_data = ctx->labs.at(lab).alms.at(alm);
+
+ std::array<CellInfo *, 2> luts{ctx->getBoundBelCell(alm_data.lut_bels[0]),
+ ctx->getBoundBelCell(alm_data.lut_bels[1])};
+ std::array<CellInfo *, 4> ffs{
+ ctx->getBoundBelCell(alm_data.ff_bels[0]), ctx->getBoundBelCell(alm_data.ff_bels[1]),
+ ctx->getBoundBelCell(alm_data.ff_bels[2]), ctx->getBoundBelCell(alm_data.ff_bels[3])};
+ // Skip empty ALMs
+ if (std::all_of(luts.begin(), luts.end(), [](CellInfo *c) { return !c; }) &&
+ std::all_of(ffs.begin(), ffs.end(), [](CellInfo *c) { return !c; }))
+ return false;
+
+ auto pos = alm_data.lut_bels[0].pos;
+ // Combinational mode - TODO: flop feedback
+ cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::MODE, alm, alm_data.l6_mode ? CycloneV::L6 : CycloneV::L5);
+ // LUT function
+ cv->bmux_r_set(CycloneV::LAB, pos, CycloneV::LUT_MASK, alm, ctx->compute_lut_mask(lab, alm));
+ // DFF/LUT output selection
+ const std::array<CycloneV::bmux_type_t, 6> mux_settings{CycloneV::TDFF0, CycloneV::TDFF1, CycloneV::TDFF1L,
+ CycloneV::BDFF0, CycloneV::BDFF1, CycloneV::BDFF1L};
+ const std::array<CycloneV::port_type_t, 6> mux_port{CycloneV::FFT0, CycloneV::FFT1, CycloneV::FFT1L,
+ CycloneV::FFB0, CycloneV::FFB1, CycloneV::FFB1L};
+ for (int i = 0; i < 6; i++) {
+ if (ctx->wires_connected(alm_data.comb_out[i / 3], ctx->get_port(CycloneV::LAB, CycloneV::pos2x(pos),
+ CycloneV::pos2y(pos), alm, mux_port[i])))
+ cv->bmux_m_set(CycloneV::LAB, pos, mux_settings[i], alm, CycloneV::NLUT);
+ }
+
+ bool is_carry = (luts[0] && luts[0]->combInfo.is_carry) || (luts[1] && luts[1]->combInfo.is_carry);
+ if (is_carry)
+ cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::ARITH_SEL, alm, CycloneV::ADDER);
+ // The carry in/out enable bits
+ if (is_carry && alm == 0 && !luts[0]->combInfo.carry_start)
+ cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::TTO_DIS, 0, true);
+ if (is_carry && alm == 5)
+ cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::BTO_DIS, 0, true);
+ // Flipflop configuration
+ const std::array<CycloneV::bmux_type_t, 2> ef_sel{CycloneV::TEF_SEL, CycloneV::BEF_SEL};
+ // This isn't a typo; the *PKREG* bits really are mirrored.
+ const std::array<CycloneV::bmux_type_t, 4> pkreg{CycloneV::TPKREG1, CycloneV::TPKREG0, CycloneV::BPKREG1,
+ CycloneV::BPKREG0};
+
+ const std::array<CycloneV::bmux_type_t, 2> clk_sel{CycloneV::TCLK_SEL, CycloneV::BCLK_SEL},
+ clr_sel{CycloneV::TCLR_SEL, CycloneV::BCLR_SEL}, sclr_dis{CycloneV::TSCLR_DIS, CycloneV::BSCLR_DIS},
+ sload_en{CycloneV::TSLOAD_EN, CycloneV::BSLOAD_EN};
+
+ const std::array<CycloneV::bmux_type_t, 3> clk_choice{CycloneV::CLK0, CycloneV::CLK1, CycloneV::CLK2};
+
+ const std::array<CycloneV::bmux_type_t, 3> clk_inv{CycloneV::CLK0_INV, CycloneV::CLK1_INV, CycloneV::CLK2_INV},
+ en_en{CycloneV::EN0_EN, CycloneV::EN1_EN, CycloneV::EN2_EN},
+ en_ninv{CycloneV::EN0_NINV, CycloneV::EN1_NINV, CycloneV::EN2_NINV};
+ const std::array<CycloneV::bmux_type_t, 2> aclr_inv{CycloneV::ACLR0_INV, CycloneV::ACLR1_INV};
+
+ for (int i = 0; i < 2; i++) {
+ // EF selection mux
+ if (ctx->wires_connected(ctx->getBelPinWire(alm_data.lut_bels[i], i ? id_F0 : id_F1), alm_data.sel_ef[i]))
+ cv->bmux_m_set(CycloneV::LAB, pos, ef_sel[i], alm, CycloneV::bmux_type_t::F);
+ }
+
+ for (int i = 0; i < 4; i++) {
+ CellInfo *ff = ffs[i];
+ if (!ff)
+ continue;
+ // PKREG (input selection)
+ if (ctx->wires_connected(alm_data.sel_ef[i / 2], alm_data.ff_in[i]))
+ cv->bmux_b_set(CycloneV::LAB, pos, pkreg[i], alm, true);
+ // Control set
+ // CLK+ENA
+ int ce_idx = alm_data.clk_ena_idx[i / 2];
+ cv->bmux_m_set(CycloneV::LAB, pos, clk_sel[i / 2], alm, clk_choice[ce_idx]);
+ if (ff->ffInfo.ctrlset.clk.inverted)
+ cv->bmux_b_set(CycloneV::LAB, pos, clk_inv[ce_idx], 0, true);
+ if (get_net_or_empty(ff, id_ENA) != nullptr) { // not using ffInfo.ctrlset, this has a fake net always to
+ // ensure different constants don't collide
+ cv->bmux_b_set(CycloneV::LAB, pos, en_en[ce_idx], 0, true);
+ cv->bmux_b_set(CycloneV::LAB, pos, en_ninv[ce_idx], 0, ff->ffInfo.ctrlset.ena.inverted);
+ } else {
+ cv->bmux_b_set(CycloneV::LAB, pos, en_en[ce_idx], 0, false);
+ }
+ // ACLR
+ int aclr_idx = alm_data.aclr_idx[i / 2];
+ cv->bmux_b_set(CycloneV::LAB, pos, clr_sel[i / 2], alm, aclr_idx == 1);
+ if (ff->ffInfo.ctrlset.aclr.inverted)
+ cv->bmux_b_set(CycloneV::LAB, pos, aclr_inv[aclr_idx], 0, true);
+ // SCLR
+ if (ff->ffInfo.ctrlset.sclr.net != nullptr) {
+ cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::SCLR_INV, 0, ff->ffInfo.ctrlset.sclr.inverted);
+ } else {
+ cv->bmux_b_set(CycloneV::LAB, pos, sclr_dis[i / 2], alm, true);
+ }
+ // SLOAD
+ if (ff->ffInfo.ctrlset.sload.net != nullptr) {
+ cv->bmux_b_set(CycloneV::LAB, pos, sload_en[i / 2], alm, true);
+ cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::SLOAD_INV, 0, ff->ffInfo.ctrlset.sload.inverted);
+ }
+ }
+ return true;
+ }
+
+ void write_ff_routing(uint32_t lab)
+ {
+ auto &lab_data = ctx->labs.at(lab);
+ auto pos = lab_data.alms.at(0).lut_bels[0].pos;
+
+ const std::array<CycloneV::bmux_type_t, 2> aclr_inp{CycloneV::ACLR0_SEL, CycloneV::ACLR1_SEL};
+ for (int i = 0; i < 2; i++) {
+ // Quartus seems to set unused ACLRs to CLKI2...
+ if (!lab_data.aclr_used[i])
+ cv->bmux_m_set(CycloneV::LAB, pos, aclr_inp[i], 0, CycloneV::CLKI2);
+ else
+ cv->bmux_m_set(CycloneV::LAB, pos, aclr_inp[i], 0, (i == 1) ? CycloneV::GIN0 : CycloneV::GIN1);
+ }
+ for (int i = 0; i < 3; i++) {
+ // Check for fabric->clock routing
+ if (ctx->wires_connected(ctx->get_port(CycloneV::LAB, CycloneV::pos2x(pos), CycloneV::pos2y(pos), -1,
+ CycloneV::DATAIN, 0),
+ lab_data.clk_wires[i]))
+ cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::CLKA_SEL, 0, CycloneV::GIN2);
+ }
+ }
+
+ void write_labs()
+ {
+ for (size_t lab = 0; lab < ctx->labs.size(); lab++) {
+ bool used = false;
+ for (uint8_t alm = 0; alm < 10; alm++)
+ used |= write_alm(lab, alm);
+ if (used)
+ write_ff_routing(lab);
+ }
+ }
+
+ void run()
+ {
+ cv->clear();
+ init();
+ write_routing();
+ write_dqs();
+ write_cells();
+ write_labs();
+ }
+};
+} // namespace
+
+void Arch::build_bitstream()
+{
+ MistralBitgen gen(getCtx());
+ gen.run();
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/mistral/constids.inc b/mistral/constids.inc
new file mode 100644
index 00000000..6bb45c3c
--- /dev/null
+++ b/mistral/constids.inc
@@ -0,0 +1,78 @@
+X(MISTRAL_COMB)
+X(MISTRAL_FF)
+X(LAB)
+X(MLAB)
+
+X(MISTRAL_IO)
+X(I)
+X(O)
+X(OE)
+X(PAD)
+X(MISTRAL_IB)
+X(MISTRAL_OB)
+
+X(A)
+X(B)
+X(C)
+X(D)
+X(E)
+X(F)
+X(E0)
+X(F0)
+X(E1)
+X(F1)
+X(DATAIN)
+X(FFT0)
+X(FFT1)
+X(FFB0)
+X(FFB1)
+X(FFT1L)
+X(FFB1L)
+X(CLKIN)
+X(ACLR)
+X(CLK)
+X(ENA)
+X(SCLR)
+X(SLOAD)
+X(SDATA)
+X(Q)
+
+X(COMBOUT)
+X(SUM_OUT)
+X(CI)
+X(SHAREIN)
+X(CO)
+X(SHAREOUT)
+
+X(CARRY_START)
+X(SHARE_START)
+X(CARRY_END)
+X(SHARE_END)
+
+X(MISTRAL_ALUT6)
+X(MISTRAL_ALUT5)
+X(MISTRAL_ALUT4)
+X(MISTRAL_ALUT3)
+X(MISTRAL_ALUT2)
+X(MISTRAL_NOT)
+X(MISTRAL_BUF)
+X(MISTRAL_CONST)
+X(MISTRAL_ALUT_ARITH)
+X(LUT)
+X(LUT0)
+X(LUT1)
+
+X(D0)
+X(D1)
+X(SO)
+
+X(WIRE)
+
+X(GND)
+X(VCC)
+X(Y)
+
+X(LOC)
+
+X(MISTRAL_CLKENA)
+X(MISTRAL_CLKBUF)
diff --git a/mistral/family.cmake b/mistral/family.cmake
new file mode 100644
index 00000000..79681ba8
--- /dev/null
+++ b/mistral/family.cmake
@@ -0,0 +1,14 @@
+set(MISTRAL_ROOT "" CACHE STRING "Mistral install path")
+
+aux_source_directory(${MISTRAL_ROOT}/lib MISTRAL_LIB_FILES)
+add_library(mistral STATIC ${MISTRAL_LIB_FILES})
+target_compile_options(mistral PRIVATE -Wno-maybe-uninitialized)
+
+find_package(LibLZMA REQUIRED)
+
+foreach(family_target ${family_targets})
+ target_include_directories(${family_target} PRIVATE ${MISTRAL_ROOT}/lib ${LIBLZMA_INCLUDE_DIRS})
+ target_link_libraries(${family_target} PRIVATE mistral ${LIBLZMA_LIBRARIES})
+ # Currently required to avoid issues with mistral (LTO means the warnings can end up in nextpnr)
+ target_link_options(${family_target} PRIVATE -Wno-maybe-uninitialized)
+endforeach()
diff --git a/mistral/globals.cc b/mistral/globals.cc
new file mode 100644
index 00000000..3203893b
--- /dev/null
+++ b/mistral/globals.cc
@@ -0,0 +1,46 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 gatecat <gatecat@ds0.me>
+ *
+ * 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 "log.h"
+#include "nextpnr.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void Arch::create_clkbuf(int x, int y)
+{
+ for (int z = 0; z < 4; z++) {
+ if (z != 2)
+ continue; // TODO: why do other Zs not work?
+ // For now we only consider the input path from general routing, other inputs like dedicated clock pins are
+ // still a TODO
+ BelId bel = add_bel(x, y, id(stringf("CLKBUF[%d]", z)), id_MISTRAL_CLKENA);
+ add_bel_pin(bel, id_A, PORT_IN, get_port(CycloneV::CMUXHG, x, y, -1, CycloneV::CLKIN, z));
+ add_bel_pin(bel, id_Q, PORT_OUT, get_port(CycloneV::CMUXHG, x, y, z, CycloneV::CLKOUT));
+ // TODO: enable pin
+ bel_data(bel).block_index = z;
+ }
+}
+
+bool Arch::is_clkbuf_cell(IdString cell_type) const
+{
+ return cell_type == id_MISTRAL_CLKENA || cell_type == id_MISTRAL_CLKBUF;
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/mistral/io.cc b/mistral/io.cc
new file mode 100644
index 00000000..3a72b001
--- /dev/null
+++ b/mistral/io.cc
@@ -0,0 +1,61 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 gatecat <gatecat@ds0.me>
+ *
+ * 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 "log.h"
+#include "nextpnr.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void Arch::create_gpio(int x, int y)
+{
+ for (int z = 0; z < 4; z++) {
+ // Notional pad wire
+ WireId pad = add_wire(x, y, id(stringf("PAD[%d]", z)));
+ BelId bel = add_bel(x, y, id(stringf("IO[%d]", z)), id_MISTRAL_IO);
+ add_bel_pin(bel, id_PAD, PORT_INOUT, pad);
+ // FIXME: is the port index of zero always correct?
+ add_bel_pin(bel, id_I, PORT_IN, get_port(CycloneV::GPIO, x, y, z, CycloneV::DATAIN, 0));
+ add_bel_pin(bel, id_OE, PORT_IN, get_port(CycloneV::GPIO, x, y, z, CycloneV::OEIN, 0));
+ add_bel_pin(bel, id_O, PORT_OUT, get_port(CycloneV::GPIO, x, y, z, CycloneV::DATAOUT, 0));
+ bel_data(bel).block_index = z;
+ }
+}
+
+bool Arch::is_io_cell(IdString cell_type) const
+{
+ // Return true if a cell is an IO buffer cell type
+ switch (cell_type.index) {
+ case ID_MISTRAL_IB:
+ case ID_MISTRAL_OB:
+ case ID_MISTRAL_IO:
+ return true;
+ default:
+ return false;
+ }
+}
+
+BelId Arch::get_io_pin_bel(const CycloneV::pin_info_t *pin) const
+{
+ auto pad = pin->pad;
+ CycloneV::pos_t pos = (pad & 0x3FFF);
+ return bel_by_block_idx(CycloneV::pos2x(pos), CycloneV::pos2y(pos), id_MISTRAL_IO, (pad >> 14));
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/mistral/lab.cc b/mistral/lab.cc
new file mode 100644
index 00000000..abd0fec3
--- /dev/null
+++ b/mistral/lab.cc
@@ -0,0 +1,969 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 gatecat <gatecat@ds0.me>
+ *
+ * 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 "design_utils.h"
+#include "log.h"
+#include "nextpnr.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// This file contains functions related to our custom LAB structure, including creating the LAB bels; checking the
+// legality of LABs; and manipulating LUT inputs and equations
+
+// LAB/ALM structure creation functions
+namespace {
+static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx)
+{
+ auto &lab = arch->labs.at(lab_idx);
+ auto &alm = lab.alms.at(z);
+ // Create the combinational part of ALMs.
+ // There are two of these, for the two LUT outputs, and these also contain the carry chain and associated logic
+ // Each one has all 8 ALM inputs as input pins. In many cases only a subset of these are used; depending on mode;
+ // and the bel-cell pin mappings are used to handle this post-placement without losing flexibility
+ for (int i = 0; i < 2; i++) {
+ // Carry/share wires are a bit tricky due to all the different permutations
+ WireId carry_in, share_in;
+ WireId carry_out, share_out;
+ if (z == 0 && i == 0) {
+ carry_in = arch->add_wire(x, y, id_CI);
+ share_in = arch->add_wire(x, y, id_SHAREIN);
+ if (y < (arch->getGridDimY() - 1)) {
+ // Carry is split at tile boundary (TTO_DIS bit), add a PIP to represent this.
+ // TODO: what about BTO_DIS, in the middle of the LAB?
+ arch->add_pip(arch->add_wire(x, y + 1, id_CO), carry_in);
+ arch->add_pip(arch->add_wire(x, y + 1, id_SHAREOUT), share_in);
+ }
+ } else {
+ // Output from last combinational unit
+ carry_in = arch->add_wire(x, y, arch->id(stringf("CARRY[%d]", (z * 2 + i) - 1)));
+ share_in = arch->add_wire(x, y, arch->id(stringf("SHARE[%d]", (z * 2 + i) - 1)));
+ }
+
+ if (z == 9 && i == 1) {
+ carry_out = arch->add_wire(x, y, id_CO);
+ share_out = arch->add_wire(x, y, id_SHAREOUT);
+ } else {
+ carry_out = arch->add_wire(x, y, arch->id(stringf("CARRY[%d]", z * 2 + i)));
+ share_out = arch->add_wire(x, y, arch->id(stringf("SHARE[%d]", z * 2 + i)));
+ }
+
+ BelId bel = arch->add_bel(x, y, arch->id(stringf("ALM%d_COMB%d", z, i)), id_MISTRAL_COMB);
+ // LUT/MUX inputs
+ arch->add_bel_pin(bel, id_A, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::A));
+ arch->add_bel_pin(bel, id_B, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::B));
+ arch->add_bel_pin(bel, id_C, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::C));
+ arch->add_bel_pin(bel, id_D, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::D));
+ arch->add_bel_pin(bel, id_E0, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::E0));
+ arch->add_bel_pin(bel, id_E1, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::E1));
+ arch->add_bel_pin(bel, id_F0, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::F0));
+ arch->add_bel_pin(bel, id_F1, PORT_IN, arch->get_port(CycloneV::LAB, x, y, z, CycloneV::F1));
+ // Carry/share chain
+ arch->add_bel_pin(bel, id_CI, PORT_IN, carry_in);
+ arch->add_bel_pin(bel, id_SHAREIN, PORT_IN, share_in);
+ arch->add_bel_pin(bel, id_CO, PORT_OUT, carry_out);
+ arch->add_bel_pin(bel, id_SHAREOUT, PORT_OUT, share_out);
+ // Combinational output
+ alm.comb_out[i] = arch->add_wire(x, y, arch->id(stringf("COMBOUT[%d]", z * 2 + i)));
+ arch->add_bel_pin(bel, id_COMBOUT, PORT_OUT, alm.comb_out[i]);
+ // Assign indexing
+ alm.lut_bels.at(i) = bel;
+ auto &b = arch->bel_data(bel);
+ b.lab_data.lab = lab_idx;
+ b.lab_data.alm = z;
+ b.lab_data.idx = i;
+ }
+ // Create the control set and E/F selection - which is per pair of FF
+ for (int i = 0; i < 2; i++) {
+ // Wires
+ alm.sel_clk[i] = arch->add_wire(x, y, arch->id(stringf("CLK%c[%d]", i ? 'B' : 'T', z)));
+ alm.sel_ena[i] = arch->add_wire(x, y, arch->id(stringf("ENA%c[%d]", i ? 'B' : 'T', z)));
+ alm.sel_aclr[i] = arch->add_wire(x, y, arch->id(stringf("ACLR%c[%d]", i ? 'B' : 'T', z)));
+ alm.sel_ef[i] = arch->add_wire(x, y, arch->id(stringf("%cEF[%d]", i ? 'B' : 'T', z)));
+ // Muxes - three CLK/ENA per LAB, two ACLR
+ for (int j = 0; j < 3; j++) {
+ arch->add_pip(lab.clk_wires[j], alm.sel_clk[i]);
+ arch->add_pip(lab.ena_wires[j], alm.sel_ena[i]);
+ if (j < 2)
+ arch->add_pip(lab.aclr_wires[j], alm.sel_aclr[i]);
+ }
+ // E/F pips
+ // Note that the F choice is mirrored, F from the other half is picked
+ arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::E1 : CycloneV::E0), alm.sel_ef[i]);
+ arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::F0 : CycloneV::F1), alm.sel_ef[i]);
+ }
+
+ // Create the flipflops and associated routing
+ const CycloneV::port_type_t outputs[4] = {CycloneV::FFT0, CycloneV::FFT1, CycloneV::FFB0, CycloneV::FFB1};
+ const CycloneV::port_type_t l_outputs[4] = {CycloneV::FFT1L, CycloneV::FFB1L};
+
+ for (int i = 0; i < 4; i++) {
+ // FF input, selected by *PKREG*
+ alm.ff_in[i] = arch->add_wire(x, y, arch->id(stringf("FFIN[%d]", (z * 4) + i)));
+ arch->add_pip(alm.comb_out[i / 2], alm.ff_in[i]);
+ arch->add_pip(alm.sel_ef[i / 2], alm.ff_in[i]);
+ // FF bel
+ BelId bel = arch->add_bel(x, y, arch->id(stringf("ALM%d_FF%d", z, i)), id_MISTRAL_FF);
+ arch->add_bel_pin(bel, id_CLK, PORT_IN, alm.sel_clk[i / 2]);
+ arch->add_bel_pin(bel, id_ENA, PORT_IN, alm.sel_ena[i / 2]);
+ arch->add_bel_pin(bel, id_ACLR, PORT_IN, alm.sel_aclr[i / 2]);
+ arch->add_bel_pin(bel, id_SCLR, PORT_IN, lab.sclr_wire);
+ arch->add_bel_pin(bel, id_SLOAD, PORT_IN, lab.sload_wire);
+ arch->add_bel_pin(bel, id_DATAIN, PORT_IN, alm.ff_in[i]);
+ arch->add_bel_pin(bel, id_SDATA, PORT_IN, alm.sel_ef[i / 2]);
+
+ // FF output
+ alm.ff_out[i] = arch->add_wire(x, y, arch->id(stringf("FFOUT[%d]", (z * 4) + i)));
+ arch->add_bel_pin(bel, id_Q, PORT_OUT, alm.ff_out[i]);
+ // Output mux (*DFF*)
+ WireId out = arch->get_port(CycloneV::LAB, x, y, z, outputs[i]);
+ arch->add_pip(alm.ff_out[i], out);
+ arch->add_pip(alm.comb_out[i / 2], out);
+ // 'L' output mux where applicable
+ if (i == 1 || i == 3) {
+ WireId l_out = arch->get_port(CycloneV::LAB, x, y, z, l_outputs[i / 2]);
+ arch->add_pip(alm.ff_out[i], l_out);
+ arch->add_pip(alm.comb_out[i / 2], l_out);
+ }
+
+ lab.alms.at(z).ff_bels.at(i) = bel;
+ auto &b = arch->bel_data(bel);
+ b.lab_data.lab = lab_idx;
+ b.lab_data.alm = z;
+ b.lab_data.idx = i;
+ }
+}
+} // namespace
+
+void Arch::create_lab(int x, int y)
+{
+ uint32_t lab_idx = labs.size();
+ labs.emplace_back();
+
+ auto &lab = labs.back();
+
+ // Create common control set configuration. This is actually a subset of what's possible, but errs on the side of
+ // caution due to incomplete documentation
+
+ // Clocks - hardcode to CLKA choices, as both CLKA and CLKB coming from general routing causes unexpected
+ // permutations
+ for (int i = 0; i < 3; i++) {
+ lab.clk_wires[i] = add_wire(x, y, id(stringf("CLK%d", i)));
+ add_pip(get_port(CycloneV::LAB, x, y, -1, CycloneV::CLKIN, 0), lab.clk_wires[i]); // dedicated routing
+ add_pip(get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 0), lab.clk_wires[i]); // general routing
+ }
+
+ // Enables - while it looks from the config like there are choices for these, it seems like EN0_SEL actually selects
+ // SCLR not ENA0 and EN1_SEL actually selects SLOAD?
+ lab.ena_wires[0] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 2);
+ lab.ena_wires[1] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 3);
+ lab.ena_wires[2] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 0);
+
+ // ACLRs - only consider general routing for now
+ lab.aclr_wires[0] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 3);
+ lab.aclr_wires[1] = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 2);
+
+ // SCLR and SLOAD - as above it seems like these might be selectable using the "EN*_SEL" bits but play it safe for
+ // now
+ lab.sclr_wire = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 3);
+ lab.sload_wire = get_port(CycloneV::LAB, x, y, -1, CycloneV::DATAIN, 1);
+
+ for (int i = 0; i < 10; i++) {
+ create_alm(this, x, y, i, lab_idx);
+ }
+}
+
+// Cell handling and annotation functions
+namespace {
+ControlSig get_ctrlsig(const Context *ctx, const CellInfo *cell, IdString port, bool explicit_const = false)
+{
+ ControlSig result;
+ result.net = get_net_or_empty(cell, port);
+ if (result.net == nullptr && explicit_const) {
+ // For ENA, 1 (and 0) are explicit control set choices even though they aren't routed, as "no ENA" still
+ // consumes a clock+ENA pair
+ CellPinState st = PIN_1;
+ result.net = ctx->nets.at((st == PIN_1) ? ctx->id("$PACKER_VCC_NET") : ctx->id("$PACKER_GND_NET")).get();
+ }
+ if (cell->pin_data.count(port))
+ result.inverted = cell->pin_data.at(port).state == PIN_INV;
+ else
+ result.inverted = false;
+ return result;
+}
+} // namespace
+
+bool Arch::is_comb_cell(IdString cell_type) const
+{
+ // Return true if a cell is a combinational cell type, to be a placed at a MISTRAL_COMB location
+ switch (cell_type.index) {
+ case ID_MISTRAL_ALUT6:
+ case ID_MISTRAL_ALUT5:
+ case ID_MISTRAL_ALUT4:
+ case ID_MISTRAL_ALUT3:
+ case ID_MISTRAL_ALUT2:
+ case ID_MISTRAL_NOT:
+ case ID_MISTRAL_CONST:
+ case ID_MISTRAL_ALUT_ARITH:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void Arch::assign_comb_info(CellInfo *cell) const
+{
+ cell->combInfo.is_carry = false;
+ cell->combInfo.is_shared = false;
+ cell->combInfo.is_extended = false;
+ cell->combInfo.carry_start = false;
+ cell->combInfo.carry_end = false;
+ cell->combInfo.chain_shared_input_count = 0;
+
+ if (cell->type == id_MISTRAL_ALUT_ARITH) {
+ cell->combInfo.is_carry = true;
+ cell->combInfo.lut_input_count = 5;
+ cell->combInfo.lut_bits_count = 32;
+
+ // This is a special case in terms of naming
+ const std::array<IdString, 5> arith_pins{id_A, id_B, id_C, id_D0, id_D1};
+ {
+ int i = 0;
+ for (auto pin : arith_pins) {
+ cell->combInfo.lut_in[i++] = get_net_or_empty(cell, pin);
+ }
+ }
+
+ const NetInfo *ci = get_net_or_empty(cell, id_CI);
+ const NetInfo *co = get_net_or_empty(cell, id_CO);
+
+ cell->combInfo.comb_out = get_net_or_empty(cell, id_SO);
+ cell->combInfo.carry_start = (ci == nullptr) || (ci->driver.cell == nullptr);
+ cell->combInfo.carry_end = (co == nullptr) || (co->users.empty());
+
+ // Compute cross-ALM routing sharing - only check the z=0 case inside ALMs
+ if (cell->constr_z > 0 && ((cell->constr_z % 2) == 0) && ci) {
+ const CellInfo *prev = ci->driver.cell;
+ if (prev != nullptr) {
+ for (int i = 0; i < 5; i++) {
+ const NetInfo *a = get_net_or_empty(cell, arith_pins[i]);
+ if (a == nullptr)
+ continue;
+ const NetInfo *b = get_net_or_empty(prev, arith_pins[i]);
+ if (a == b)
+ ++cell->combInfo.chain_shared_input_count;
+ }
+ }
+ }
+
+ } else {
+ cell->combInfo.lut_input_count = 0;
+ switch (cell->type.index) {
+ case ID_MISTRAL_ALUT6:
+ ++cell->combInfo.lut_input_count;
+ cell->combInfo.lut_in[5] = get_net_or_empty(cell, id_F);
+ [[fallthrough]];
+ case ID_MISTRAL_ALUT5:
+ ++cell->combInfo.lut_input_count;
+ cell->combInfo.lut_in[4] = get_net_or_empty(cell, id_E);
+ [[fallthrough]];
+ case ID_MISTRAL_ALUT4:
+ ++cell->combInfo.lut_input_count;
+ cell->combInfo.lut_in[3] = get_net_or_empty(cell, id_D);
+ [[fallthrough]];
+ case ID_MISTRAL_ALUT3:
+ ++cell->combInfo.lut_input_count;
+ cell->combInfo.lut_in[2] = get_net_or_empty(cell, id_C);
+ [[fallthrough]];
+ case ID_MISTRAL_ALUT2:
+ ++cell->combInfo.lut_input_count;
+ cell->combInfo.lut_in[1] = get_net_or_empty(cell, id_B);
+ [[fallthrough]];
+ case ID_MISTRAL_BUF: // used to route through to FFs etc
+ case ID_MISTRAL_NOT: // used for inverters that map to LUTs
+ ++cell->combInfo.lut_input_count;
+ cell->combInfo.lut_in[0] = get_net_or_empty(cell, id_A);
+ [[fallthrough]];
+ case ID_MISTRAL_CONST:
+ // MISTRAL_CONST is a nextpnr-inserted cell type for 0-input, constant-generating LUTs
+ break;
+ default:
+ log_error("unexpected combinational cell type %s\n", getCtx()->nameOf(cell->type));
+ }
+ // Note that this relationship won't hold for extended mode, when that is supported
+ cell->combInfo.lut_bits_count = (1 << cell->combInfo.lut_input_count);
+ }
+ cell->combInfo.used_lut_input_count = 0;
+ for (int i = 0; i < cell->combInfo.lut_input_count; i++)
+ if (cell->combInfo.lut_in[i])
+ ++cell->combInfo.used_lut_input_count;
+}
+
+void Arch::assign_ff_info(CellInfo *cell) const
+{
+ cell->ffInfo.ctrlset.clk = get_ctrlsig(getCtx(), cell, id_CLK);
+ cell->ffInfo.ctrlset.ena = get_ctrlsig(getCtx(), cell, id_ENA, true);
+ cell->ffInfo.ctrlset.aclr = get_ctrlsig(getCtx(), cell, id_ACLR);
+ cell->ffInfo.ctrlset.sclr = get_ctrlsig(getCtx(), cell, id_SCLR);
+ cell->ffInfo.ctrlset.sload = get_ctrlsig(getCtx(), cell, id_SLOAD);
+ // If SCLR is used, but SLOAD isn't, then it seems like we need to pretend as if SLOAD is connected GND (so set
+ // [BT]SLOAD_EN inside the ALMs, and clear SLOAD_INV)
+ if (cell->ffInfo.ctrlset.sclr.net != nullptr && cell->ffInfo.ctrlset.sload.net == nullptr) {
+ cell->ffInfo.ctrlset.sload.net = nets.at(id("$PACKER_GND_NET")).get();
+ cell->ffInfo.ctrlset.sload.inverted = false;
+ }
+
+ cell->ffInfo.sdata = get_net_or_empty(cell, id_SDATA);
+ cell->ffInfo.datain = get_net_or_empty(cell, id_DATAIN);
+}
+
+// Validity checking functions
+bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const
+{
+ auto &alm_data = labs.at(lab).alms.at(alm);
+ // Get cells into an array for fast access
+ std::array<const CellInfo *, 2> luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])};
+ std::array<const CellInfo *, 4> ffs{getBoundBelCell(alm_data.ff_bels[0]), getBoundBelCell(alm_data.ff_bels[1]),
+ getBoundBelCell(alm_data.ff_bels[2]), getBoundBelCell(alm_data.ff_bels[3])};
+ int used_lut_bits = 0;
+
+ int total_lut_inputs = 0;
+ // TODO: for more complex modes like extended/arithmetic, it might not always be possible for any LUT input to map
+ // to any of the ALM half inputs particularly shared and extended mode will need more thought and probably for this
+ // to be revisited
+ for (int i = 0; i < 2; i++) {
+ if (!luts[i])
+ continue;
+ total_lut_inputs += luts[i]->combInfo.lut_input_count;
+ used_lut_bits += luts[i]->combInfo.lut_bits_count;
+ }
+ // An ALM only has 64 bits of storage. In theory some of these cases might be legal because of overlap between the
+ // two functions, but the current placer is unlikely to stumble upon these cases frequently without anything to
+ // guide it, and the cost of checking them here almost certainly outweighs any marginal benefit in supporting them,
+ // at least for now.
+ if (used_lut_bits > 64)
+ return false;
+
+ if (total_lut_inputs > 8) {
+ NPNR_ASSERT(luts[0] && luts[1]); // something has gone badly wrong if this fails!
+ // Make sure that LUT inputs are not overprovisioned
+ int shared_lut_inputs = 0;
+ // Even though this N^2 search looks inefficient, it's unlikely a set lookup or similar is going to be much
+ // better given the low N.
+ for (int i = 0; i < luts[1]->combInfo.lut_input_count; i++) {
+ const NetInfo *sig = luts[1]->combInfo.lut_in[i];
+ for (int j = 0; j < luts[0]->combInfo.lut_input_count; j++) {
+ if (sig == luts[0]->combInfo.lut_in[j]) {
+ ++shared_lut_inputs;
+ break;
+ }
+ }
+ }
+ if ((total_lut_inputs - shared_lut_inputs) > 8)
+ return false;
+ }
+
+ bool carry_mode = false;
+
+ // No mixing of carry and non-carry
+ if (luts[0] && luts[1] && luts[0]->combInfo.is_carry != luts[1]->combInfo.is_carry)
+ return false;
+
+ // For each ALM half; check FF control set sharing and input routeability
+ for (int i = 0; i < 2; i++) {
+ // There are two ways to route from the fabric into FF data - either routing through a LUT or using the E/F
+ // signals and SLOAD=1 (*PKREF*)
+ bool route_thru_lut_avail = !luts[i] && !carry_mode && (total_lut_inputs < 8) && (used_lut_bits < 64);
+ // E/F is available if this LUT is using 3 or fewer inputs - this is conservative and sharing can probably
+ // improve this situation. (1 - i) because the F input to EF_SEL is mirrored.
+ bool ef_available = (!luts[1 - i] || (luts[1 - i]->combInfo.used_lut_input_count <= 2));
+ // Control set checking
+ bool found_ff = false;
+
+ FFControlSet ctrlset;
+ for (int j = 0; j < 2; j++) {
+ const CellInfo *ff = ffs[i * 2 + j];
+ if (!ff)
+ continue;
+ if (j == 1)
+ return false; // TODO: why are these FFs broken?
+ if (found_ff) {
+ // Two FFs in the same half with an incompatible control set
+ if (ctrlset != ff->ffInfo.ctrlset)
+ return false;
+ } else {
+ ctrlset = ff->ffInfo.ctrlset;
+ }
+ // SDATA must use the E/F input
+ // TODO: rare case of two FFs with the same SDATA in the same ALM half
+ if (ff->ffInfo.sdata) {
+ if (!ef_available)
+ return false;
+ ef_available = false;
+ }
+ // Find a way of routing the input through fabric, if it's not driven by the LUT
+ if (ff->ffInfo.datain && (!luts[i] || (ff->ffInfo.datain != luts[i]->combInfo.comb_out))) {
+ if (route_thru_lut_avail)
+ route_thru_lut_avail = false;
+ else if (ef_available)
+ ef_available = false;
+ else
+ return false;
+ }
+ found_ff = true;
+ }
+ }
+
+ return true;
+}
+
+void Arch::update_alm_input_count(uint32_t lab, uint8_t alm)
+{
+ // TODO: duplication with above
+ auto &alm_data = labs.at(lab).alms.at(alm);
+ // Get cells into an array for fast access
+ std::array<const CellInfo *, 2> luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])};
+ std::array<const CellInfo *, 4> ffs{getBoundBelCell(alm_data.ff_bels[0]), getBoundBelCell(alm_data.ff_bels[1]),
+ getBoundBelCell(alm_data.ff_bels[2]), getBoundBelCell(alm_data.ff_bels[3])};
+ int total_inputs = 0;
+ int total_lut_inputs = 0;
+ for (int i = 0; i < 2; i++) {
+ if (!luts[i])
+ continue;
+ total_lut_inputs += luts[i]->combInfo.used_lut_input_count - luts[i]->combInfo.chain_shared_input_count;
+ }
+ int shared_lut_inputs = 0;
+ if (luts[0] && luts[1]) {
+ for (int i = 0; i < luts[1]->combInfo.lut_input_count; i++) {
+ const NetInfo *sig = luts[1]->combInfo.lut_in[i];
+ if (!sig)
+ continue;
+ for (int j = 0; j < luts[0]->combInfo.lut_input_count; j++) {
+ if (sig == luts[0]->combInfo.lut_in[j]) {
+ ++shared_lut_inputs;
+ break;
+ }
+ }
+ if (shared_lut_inputs >= 2) {
+ // only 2 inputs have guaranteed sharing, without routeability based LUT permutation at least
+ break;
+ }
+ }
+ }
+ total_inputs = std::max(0, total_lut_inputs - shared_lut_inputs);
+ for (int i = 0; i < 4; i++) {
+ const CellInfo *ff = ffs[i];
+ if (!ff)
+ continue;
+ if (ff->ffInfo.sdata)
+ ++total_inputs;
+ // FF input doesn't consume routing resources if driven by associated LUT
+ if (ff->ffInfo.datain && (!luts[i / 2] || ff->ffInfo.datain != luts[i / 2]->combInfo.comb_out))
+ ++total_inputs;
+ }
+ alm_data.unique_input_count = total_inputs;
+}
+
+bool Arch::check_lab_input_count(uint32_t lab) const
+{
+ // There are only 46 TD signals available to route signals from general routing to the ALM input. Currently, we
+ // check the total sum of ALM inputs is less than 42; 46 minus 4 FF control inputs. This is a conservative check for
+ // several reasons, because LD signals are also available for feedback routing from ALM output to input, and because
+ // TD signals may be shared if the same net routes to multiple ALMs. But these cases will need careful handling and
+ // LUT permutation during routing to be useful; and in any event conservative LAB packing will help nextpnr's
+ // currently perfunctory place and route algorithms to achieve satisfactory runtimes.
+ int count = 0;
+ auto &lab_data = labs.at(lab);
+ for (int i = 0; i < 10; i++) {
+ count += lab_data.alms.at(i).unique_input_count;
+ }
+ return (count <= 42);
+}
+
+namespace {
+bool check_assign_sig(ControlSig &sig_set, const ControlSig &sig)
+{
+ if (sig.net == nullptr) {
+ return true;
+ } else if (sig_set == sig) {
+ return true;
+ } else if (sig_set.net == nullptr) {
+ sig_set = sig;
+ return true;
+ } else {
+ return false;
+ }
+};
+
+template <size_t N> bool check_assign_sig(std::array<ControlSig, N> &sig_set, const ControlSig &sig)
+{
+ if (sig.net == nullptr)
+ return true;
+ for (size_t i = 0; i < N; i++)
+ if (sig_set[i] == sig) {
+ return true;
+ } else if (sig_set[i].net == nullptr) {
+ sig_set[i] = sig;
+ return true;
+ }
+ return false;
+};
+
+// DATAIN mapping rules - which LAB DATAIN signals can be used for ENA and ACLR
+static constexpr std::array<int, 3> ena_datain{2, 3, 0};
+static constexpr std::array<int, 2> aclr_datain{3, 2};
+
+struct LabCtrlSetWorker
+{
+
+ ControlSig clk{}, sload{}, sclr{};
+ std::array<ControlSig, 2> aclr{};
+ std::array<ControlSig, 3> ena{};
+
+ std::array<ControlSig, 4> datain{};
+
+ bool run(const Arch *arch, uint32_t lab)
+ {
+ // Strictly speaking the constraint is up to 2 unique CLK and 3 CLK+ENA pairs. For now we simplify this to 1 CLK
+ // and 3 ENA though.
+ for (uint8_t alm = 0; alm < 10; alm++) {
+ for (uint8_t i = 0; i < 4; i++) {
+ const CellInfo *ff = arch->getBoundBelCell(arch->labs.at(lab).alms.at(alm).ff_bels.at(i));
+ if (ff == nullptr)
+ continue;
+
+ if (!check_assign_sig(clk, ff->ffInfo.ctrlset.clk))
+ return false;
+ if (!check_assign_sig(sload, ff->ffInfo.ctrlset.sload))
+ return false;
+ if (!check_assign_sig(sclr, ff->ffInfo.ctrlset.sclr))
+ return false;
+ if (!check_assign_sig(aclr, ff->ffInfo.ctrlset.aclr))
+ return false;
+ if (!check_assign_sig(ena, ff->ffInfo.ctrlset.ena))
+ return false;
+ }
+ }
+ // Check for overuse of the shared, LAB-wide datain signals
+ if (clk.net != nullptr && !clk.net->is_global)
+ if (!check_assign_sig(datain[0], clk)) // CLK only needs DATAIN[0] if it's not global
+ return false;
+ if (!check_assign_sig(datain[1], sload))
+ return false;
+ if (!check_assign_sig(datain[3], sclr))
+ return false;
+ for (const auto &aclr_sig : aclr) {
+ // Check both possibilities that ACLR can map to
+ // TODO: ACLR could be global, too
+ if (check_assign_sig(datain[aclr_datain[0]], aclr_sig))
+ continue;
+ if (check_assign_sig(datain[aclr_datain[1]], aclr_sig))
+ continue;
+ // Failed to find any free ACLR-capable DATAIN
+ return false;
+ }
+ for (const auto &ena_sig : ena) {
+ // Check all 3 possibilities that ACLR can map to
+ // TODO: ACLR could be global, too
+ if (check_assign_sig(datain[ena_datain[0]], ena_sig))
+ continue;
+ if (check_assign_sig(datain[ena_datain[1]], ena_sig))
+ continue;
+ if (check_assign_sig(datain[ena_datain[2]], ena_sig))
+ continue;
+ // Failed to find any free ENA-capable DATAIN
+ return false;
+ }
+ return true;
+ }
+};
+
+}; // namespace
+
+bool Arch::is_lab_ctrlset_legal(uint32_t lab) const
+{
+ LabCtrlSetWorker worker;
+ return worker.run(this, lab);
+}
+
+void Arch::lab_pre_route()
+{
+ log_info("Preparing LABs for routing...\n");
+ for (uint32_t lab = 0; lab < labs.size(); lab++) {
+ assign_control_sets(lab);
+ for (uint8_t alm = 0; alm < 10; alm++) {
+ reassign_alm_inputs(lab, alm);
+ }
+ }
+}
+
+void Arch::assign_control_sets(uint32_t lab)
+{
+ // Set up reservations for checkPipAvail for control set signals
+ // This will be needed because clock and CE are routed together and must be kept together, there isn't free choice
+ // e.g. CLK0 & ENA0 must be use for one control set, and CLK1 & ENA1 for another, they can't be mixed and matched
+ // Similarly for how inverted & noninverted variants must be kept separate
+ LabCtrlSetWorker worker;
+ bool legal = worker.run(this, lab);
+ NPNR_ASSERT(legal);
+ auto &lab_data = labs.at(lab);
+
+ for (int j = 0; j < 2; j++) {
+ lab_data.aclr_used[j] = false;
+ }
+
+ for (uint8_t alm = 0; alm < 10; alm++) {
+ auto &alm_data = lab_data.alms.at(alm);
+ for (uint8_t i = 0; i < 4; i++) {
+ BelId ff_bel = alm_data.ff_bels.at(i);
+ const CellInfo *ff = getBoundBelCell(ff_bel);
+ if (ff == nullptr)
+ continue;
+ ControlSig ena_sig = ff->ffInfo.ctrlset.ena;
+ WireId clk_wire = getBelPinWire(ff_bel, id_CLK);
+ WireId ena_wire = getBelPinWire(ff_bel, id_ENA);
+ for (int j = 0; j < 3; j++) {
+ if (ena_sig == worker.datain[ena_datain[j]]) {
+ if (getCtx()->debug) {
+ log_info("Assigned CLK/ENA set %d to FF %s (%s)\n", i, nameOf(ff), getCtx()->nameOfBel(ff_bel));
+ }
+ // TODO: lock clock according to ENA choice, too, when we support two clocks per ALM
+ reserve_route(lab_data.clk_wires[0], clk_wire);
+ reserve_route(lab_data.ena_wires[j], ena_wire);
+ alm_data.clk_ena_idx[i / 2] = j;
+ break;
+ }
+ }
+
+ ControlSig aclr_sig = ff->ffInfo.ctrlset.aclr;
+ WireId aclr_wire = getBelPinWire(ff_bel, id_ACLR);
+ for (int j = 0; j < 2; j++) {
+ // TODO: could be global ACLR, too
+ if (aclr_sig == worker.datain[aclr_datain[j]]) {
+ if (getCtx()->debug) {
+ log_info("Assigned ACLR set %d to FF %s (%s)\n", i, nameOf(ff), getCtx()->nameOfBel(ff_bel));
+ }
+ reserve_route(lab_data.aclr_wires[j], aclr_wire);
+ lab_data.aclr_used[j] = (aclr_sig.net != nullptr);
+ alm_data.aclr_idx[i / 2] = j;
+ break;
+ }
+ }
+ }
+ }
+}
+
+namespace {
+// Gets the name of logical LUT pin i for a given cell
+static IdString get_lut_pin(CellInfo *cell, int i)
+{
+ const std::array<IdString, 6> log_pins{id_A, id_B, id_C, id_D, id_E, id_F};
+ const std::array<IdString, 5> log_pins_arith{id_A, id_B, id_C, id_D0, id_D1};
+ return (cell->type == id_MISTRAL_ALUT_ARITH) ? log_pins_arith.at(i) : log_pins.at(i);
+}
+
+static void assign_lut6_inputs(CellInfo *cell, int lut)
+{
+ std::array<IdString, 6> phys_pins{id_A, id_B, id_C, id_D, (lut == 1) ? id_E1 : id_E0, (lut == 1) ? id_F1 : id_F0};
+ int phys_idx = 0;
+ for (int i = 0; i < 6; i++) {
+ IdString log = get_lut_pin(cell, i);
+ if (!cell->ports.count(log) || cell->ports.at(log).net == nullptr)
+ continue;
+ cell->pin_data[log].bel_pins.clear();
+ cell->pin_data[log].bel_pins.push_back(phys_pins.at(phys_idx++));
+ }
+}
+} // namespace
+
+void Arch::reassign_alm_inputs(uint32_t lab, uint8_t alm)
+{
+ // Based on the usage of LUTs inside the ALM, set up cell-bel pin map for the combinational cells in the ALM
+ // so that each physical bel pin is only used for one net; and the logical functions can be implemented correctly.
+ // This function should also insert route-through LUTs to legalise flipflop inputs as needed.
+ auto &alm_data = labs.at(lab).alms.at(alm);
+ alm_data.l6_mode = false;
+ std::array<CellInfo *, 2> luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])};
+ std::array<CellInfo *, 4> ffs{getBoundBelCell(alm_data.ff_bels[0]), getBoundBelCell(alm_data.ff_bels[1]),
+ getBoundBelCell(alm_data.ff_bels[2]), getBoundBelCell(alm_data.ff_bels[3])};
+
+ for (int i = 0; i < 2; i++) {
+ // Currently we treat LUT6s as a special case, as they never share inputs
+ if (luts[i] != nullptr && luts[i]->type == id_MISTRAL_ALUT6) {
+ alm_data.l6_mode = true;
+ NPNR_ASSERT(luts[1 - i] == nullptr); // only allow one LUT6 per ALM and no other LUTs
+ assign_lut6_inputs(luts[i], i);
+ }
+ }
+
+ if (!alm_data.l6_mode) {
+ // In L5 mode; which is what we use in this case
+ // - A and B are shared
+ // - C, E0, and F0 are exclusive to the top LUT5 secion
+ // - D, E1, and F1 are exclusive to the bottom LUT5 section
+ // First find up to two shared inputs
+ std::unordered_map<IdString, int> shared_nets;
+ if (luts[0] && luts[1]) {
+ for (int i = 0; i < luts[0]->combInfo.lut_input_count; i++) {
+ for (int j = 0; j < luts[1]->combInfo.lut_input_count; j++) {
+ if (luts[0]->combInfo.lut_in[i] == nullptr)
+ continue;
+ if (luts[0]->combInfo.lut_in[i] != luts[1]->combInfo.lut_in[j])
+ continue;
+ IdString net = luts[0]->combInfo.lut_in[i]->name;
+ if (shared_nets.count(net))
+ continue;
+ int idx = int(shared_nets.size());
+ shared_nets[net] = idx;
+ if (shared_nets.size() >= 2)
+ goto shared_search_done;
+ }
+ }
+ shared_search_done:;
+ }
+ // A and B can be used for half-specific nets if not assigned to shared nets
+ bool a_avail = shared_nets.size() == 0, b_avail = shared_nets.size() <= 1;
+ // Do the actual port assignment
+ for (int i = 0; i < 2; i++) {
+ if (!luts[i])
+ continue;
+ // Work out which physical ports are available
+ std::vector<IdString> avail_phys_ports;
+ // D/C always available and dedicated to the half, in L5 mode
+ avail_phys_ports.push_back((i == 1) ? id_D : id_C);
+ // In arithmetic mode, Ei can only be used for D0 and Fi can only be used for D1
+ // otherwise, these are general and dedicated to one half
+ if (!luts[i]->combInfo.is_carry) {
+ avail_phys_ports.push_back((i == 1) ? id_E1 : id_E0);
+ avail_phys_ports.push_back((i == 1) ? id_F1 : id_F0);
+ }
+ // A and B might be used for shared signals, or already used by the other half
+ if (b_avail)
+ avail_phys_ports.push_back(id_B);
+ if (a_avail)
+ avail_phys_ports.push_back(id_A);
+ int phys_idx = 0;
+
+ for (int j = 0; j < luts[i]->combInfo.lut_input_count; j++) {
+ IdString log = get_lut_pin(luts[i], j);
+ auto &bel_pins = luts[i]->pin_data[log].bel_pins;
+ bel_pins.clear();
+
+ NetInfo *net = get_net_or_empty(luts[i], log);
+ if (net == nullptr) {
+ // Disconnected inputs don't need to be allocated a pin, because the router won't be routing these
+ continue;
+ } else if (shared_nets.count(net->name)) {
+ // This pin is to be allocated one of the shared nets
+ bel_pins.push_back(shared_nets.at(net->name) ? id_B : id_A);
+ } else if (log == id_D0) {
+ // Arithmetic
+ bel_pins.push_back((i == 1) ? id_E1 : id_E0); // reserved
+ } else if (log == id_D1) {
+ bel_pins.push_back((i == 1) ? id_F1 : id_F0); // reserved
+ } else {
+ // Allocate from the general pool of available physical pins
+ IdString phys = avail_phys_ports.at(phys_idx++);
+ bel_pins.push_back(phys);
+ // Mark A/B unavailable for the other LUT, if needed
+ if (phys == id_A)
+ a_avail = false;
+ else if (phys == id_B)
+ b_avail = false;
+ }
+ }
+ }
+ }
+
+ // FF route-through insertion
+ for (int i = 0; i < 2; i++) {
+ // FF route-through will never be inserted if LUT is used
+ if (luts[i])
+ continue;
+ for (int j = 0; j < 2; j++) {
+ CellInfo *ff = ffs[i * 2 + j];
+ if (!ff || !ff->ffInfo.datain || alm_data.l6_mode)
+ continue;
+ CellInfo *rt_lut = createCell(id(stringf("%s$ROUTETHRU", nameOf(ff))), id_MISTRAL_BUF);
+ rt_lut->addInput(id_A);
+ rt_lut->addOutput(id_Q);
+ // Disconnect the original data input to the FF, and connect it to the route-thru LUT instead
+ NetInfo *datain = get_net_or_empty(ff, id_DATAIN);
+ disconnect_port(getCtx(), ff, id_DATAIN);
+ connect_port(getCtx(), datain, rt_lut, id_A);
+ connect_ports(getCtx(), rt_lut, id_Q, ff, id_DATAIN);
+ // Assign route-thru LUT physical ports, input goes to the first half-specific input
+ rt_lut->pin_data[id_A].bel_pins.push_back(i ? id_D : id_C);
+ rt_lut->pin_data[id_Q].bel_pins.push_back(id_COMBOUT);
+ assign_comb_info(rt_lut);
+ // Place the route-thru LUT at the relevant combinational bel
+ bindBel(alm_data.lut_bels[i], rt_lut, STRENGTH_STRONG);
+ break;
+ }
+ }
+
+ // TODO: in the future, as well as the reassignment here we will also have pseudo PIPs in front of the ALM so that
+ // the router can permute LUTs for routeability; too. Here we will need to lock out some of those PIPs depending on
+ // the usage of the ALM, as not all inputs are always interchangeable.
+ // Get cells into an array for fast access
+}
+
+// This default cell-bel pin mapping is used to provide estimates during placement only. It will have errors and
+// overlaps and a correct mapping will be resolved twixt placement and routing
+const std::unordered_map<IdString, IdString> Arch::comb_pinmap = {
+ {id_A, id_F0}, // fastest input first
+ {id_B, id_E0}, {id_C, id_D}, {id_D, id_C}, {id_D0, id_C}, {id_D1, id_B},
+ {id_E, id_B}, {id_F, id_A}, {id_Q, id_COMBOUT}, {id_SO, id_COMBOUT},
+};
+
+namespace {
+// gets the value of the ith LUT init property of a given cell
+uint64_t get_lut_init(const CellInfo *cell, int i)
+{
+ if (cell->type == id_MISTRAL_NOT) {
+ return 1;
+ } else if (cell->type == id_MISTRAL_BUF) {
+ return 2;
+ } else {
+ IdString prop;
+ if (cell->type == id_MISTRAL_ALUT_ARITH)
+ prop = (i == 1) ? id_LUT1 : id_LUT0;
+ else
+ prop = id_LUT;
+ auto fnd = cell->params.find(prop);
+ if (fnd == cell->params.end())
+ return 0;
+ else
+ return fnd->second.as_int64();
+ }
+}
+// gets the state of a physical pin when evaluating the a given bit of LUT init for
+bool get_phys_pin_val(bool l6_mode, bool arith_mode, int bit, IdString pin)
+{
+ switch (pin.index) {
+ case ID_A:
+ return (bit >> 0) & 0x1;
+ case ID_B:
+ return (bit >> 1) & 0x1;
+ case ID_C:
+ return (l6_mode && bit >= 32) ? ((bit >> 3) & 0x1) : ((bit >> 2) & 0x1);
+ case ID_D:
+ return (l6_mode && bit < 32) ? ((bit >> 3) & 0x1) : ((bit >> 2) & 0x1);
+ case ID_E0:
+ case ID_E1:
+ return l6_mode ? ((bit >> 5) & 0x1) : ((bit >> 3) & 0x1);
+ case ID_F0:
+ case ID_F1:
+ return arith_mode ? ((bit >> 3) & 0x1) : ((bit >> 4) & 0x1);
+ default:
+ NPNR_ASSERT_FALSE("unknown physical pin!");
+ }
+}
+} // namespace
+
+uint64_t Arch::compute_lut_mask(uint32_t lab, uint8_t alm)
+{
+ uint64_t mask = 0;
+ auto &alm_data = labs.at(lab).alms.at(alm);
+ std::array<CellInfo *, 2> luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])};
+
+ for (int i = 0; i < 2; i++) {
+ CellInfo *lut = luts[i];
+ if (!lut)
+ continue;
+ int offset = ((i == 1) && !alm_data.l6_mode) ? 32 : 0;
+ bool arith = lut->combInfo.is_carry;
+ for (int j = 0; j < (alm_data.l6_mode ? 64 : 32); j++) {
+ // Evaluate LUT function at this point
+ uint64_t init = get_lut_init(lut, (arith && j >= 16) ? 1 : 0);
+ int index = 0;
+ for (int k = 0; k < lut->combInfo.lut_input_count; k++) {
+ IdString log_pin = get_lut_pin(lut, k);
+ int init_idx = k;
+ if (arith) {
+ // D0 only affects lower half; D1 upper half
+ if (k == 3 && j >= 16)
+ continue;
+ if (k == 4) {
+ if (j < 16)
+ continue;
+ else
+ init_idx = 3;
+ }
+ }
+ CellPinState state = lut->get_pin_state(log_pin);
+ if (state == PIN_0)
+ continue;
+ else if (state == PIN_1)
+ index |= (1 << init_idx);
+ // Ignore if no associated physical pin
+ if (get_net_or_empty(lut, log_pin) == nullptr || lut->pin_data.at(log_pin).bel_pins.empty())
+ continue;
+ // ALM inputs appear to be inverted by default (TODO: check!)
+ // so only invert if an inverter has _not_ been folded into the pin
+ bool inverted = (state != PIN_INV);
+ // Depermute physical pin
+ IdString phys_pin = lut->pin_data.at(log_pin).bel_pins.at(0);
+ if (get_phys_pin_val(alm_data.l6_mode, arith, j, phys_pin) != inverted)
+ index |= (1 << init_idx);
+ }
+ if ((init >> index) & 0x1) {
+ mask |= (1ULL << uint64_t(j + offset));
+ }
+ }
+ }
+
+ // TODO: always inverted, or just certain paths?
+ mask = ~mask;
+
+#if 1
+ if (getCtx()->debug) {
+ auto pos = alm_data.lut_bels[0].pos;
+ log("ALM %03d.%03d.%d\n", CycloneV::pos2x(pos), CycloneV::pos2y(pos), alm);
+ for (int i = 0; i < 2; i++) {
+ log(" LUT%d: ", i);
+ if (luts[i]) {
+ log("%s:%s", nameOf(luts[i]), nameOf(luts[i]->type));
+ for (auto &pin : luts[i]->pin_data) {
+ if (!luts[i]->ports.count(pin.first) || luts[i]->ports.at(pin.first).type != PORT_IN)
+ continue;
+ log(" %s:", nameOf(pin.first));
+ if (pin.second.state == PIN_0)
+ log("0");
+ else if (pin.second.state == PIN_1)
+ log("1");
+ else if (pin.second.state == PIN_INV)
+ log("~");
+ for (auto bp : pin.second.bel_pins)
+ log("%s", nameOf(bp));
+ }
+ } else {
+ log("<null>");
+ }
+ log("\n");
+ }
+ log("INIT: %016lx\n", mask);
+ log("\n");
+ }
+#endif
+
+ return mask;
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/mistral/main.cc b/mistral/main.cc
new file mode 100644
index 00000000..9147a68b
--- /dev/null
+++ b/mistral/main.cc
@@ -0,0 +1,105 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 gatecat <gatecat@ds0.me>
+ *
+ * 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.
+ *
+ */
+
+#ifdef MAIN_EXECUTABLE
+
+#include <fstream>
+#include "command.h"
+#include "design_utils.h"
+#include "jsonwrite.h"
+#include "log.h"
+#include "timing.h"
+
+USING_NEXTPNR_NAMESPACE
+
+class MistralCommandHandler : public CommandHandler
+{
+ public:
+ MistralCommandHandler(int argc, char **argv);
+ virtual ~MistralCommandHandler(){};
+ std::unique_ptr<Context> createContext(std::unordered_map<std::string, Property> &values) override;
+ void setupArchContext(Context *ctx) override{};
+ void customBitstream(Context *ctx) override;
+ void customAfterLoad(Context *ctx) override;
+
+ protected:
+ po::options_description getArchOptions() override;
+};
+
+MistralCommandHandler::MistralCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {}
+
+po::options_description MistralCommandHandler::getArchOptions()
+{
+ po::options_description specific("Architecture specific options");
+ specific.add_options()("mistral", po::value<std::string>(), "path to mistral root");
+ specific.add_options()("device", po::value<std::string>(), "device name (e.g. 5CSEBA6U23I7)");
+ specific.add_options()("qsf", po::value<std::string>(), "path to QSF constraints file");
+ specific.add_options()("rbf", po::value<std::string>(), "RBF bitstream to write");
+
+ return specific;
+}
+
+void MistralCommandHandler::customBitstream(Context *ctx)
+{
+ if (vm.count("rbf")) {
+ std::string filename = vm["rbf"].as<std::string>();
+ ctx->build_bitstream();
+ std::vector<uint8_t> data;
+ ctx->cyclonev->rbf_save(data);
+
+ std::ofstream out(filename, std::ios::binary);
+ if (!out)
+ log_error("Failed to open output RBF file %s.\n", filename.c_str());
+ out.write(reinterpret_cast<const char *>(data.data()), data.size());
+ }
+}
+
+std::unique_ptr<Context> MistralCommandHandler::createContext(std::unordered_map<std::string, Property> &values)
+{
+ ArchArgs chipArgs;
+ if (!vm.count("mistral")) {
+ log_error("mistral must be specified on the command line\n");
+ }
+ if (!vm.count("device")) {
+ log_error("device must be specified on the command line (e.g. --device 5CSEBA6U23I7)\n");
+ }
+ chipArgs.mistral_root = vm["mistral"].as<std::string>();
+ chipArgs.device = vm["device"].as<std::string>();
+ auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
+ return ctx;
+}
+
+void MistralCommandHandler::customAfterLoad(Context *ctx)
+{
+ if (vm.count("qsf")) {
+ std::string filename = vm["qsf"].as<std::string>();
+ std::ifstream in(filename);
+ if (!in)
+ log_error("Failed to open input QSF file %s.\n", filename.c_str());
+ ctx->read_qsf(in);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ MistralCommandHandler handler(argc, argv);
+ return handler.exec();
+}
+
+#endif
diff --git a/mistral/pack.cc b/mistral/pack.cc
new file mode 100644
index 00000000..90fbfd78
--- /dev/null
+++ b/mistral/pack.cc
@@ -0,0 +1,365 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 gatecat <gatecat@ds0.me>
+ *
+ * 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 "design_utils.h"
+#include "log.h"
+#include "nextpnr.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+namespace {
+struct MistralPacker
+{
+ MistralPacker(Context *ctx) : ctx(ctx){};
+ Context *ctx;
+
+ NetInfo *gnd_net, *vcc_net;
+
+ void init_constant_nets()
+ {
+ CellInfo *gnd_drv = ctx->createCell(ctx->id("$PACKER_GND_DRV"), id_MISTRAL_CONST);
+ gnd_drv->params[id_LUT] = 0;
+ gnd_drv->addOutput(id_Q);
+ CellInfo *vcc_drv = ctx->createCell(ctx->id("$PACKER_VCC_DRV"), id_MISTRAL_CONST);
+ vcc_drv->params[id_LUT] = 1;
+ vcc_drv->addOutput(id_Q);
+ gnd_net = ctx->createNet(ctx->id("$PACKER_GND_NET"));
+ vcc_net = ctx->createNet(ctx->id("$PACKER_VCC_NET"));
+ connect_port(ctx, gnd_net, gnd_drv, id_Q);
+ connect_port(ctx, vcc_net, vcc_drv, id_Q);
+ }
+
+ CellPinState get_pin_needed_muxval(CellInfo *cell, IdString port)
+ {
+ NetInfo *net = get_net_or_empty(cell, port);
+ if (net == nullptr || net->driver.cell == nullptr) {
+ // Pin is disconnected
+ // If a mux value exists already, honour it
+ CellPinState exist_mux = cell->get_pin_state(port);
+ if (exist_mux != PIN_SIG)
+ return exist_mux;
+ // Otherwise, look up the default value and use that
+ CellPinStyle pin_style = ctx->get_cell_pin_style(cell, port);
+ if ((pin_style & PINDEF_MASK) == PINDEF_0)
+ return PIN_0;
+ else if ((pin_style & PINDEF_MASK) == PINDEF_1)
+ return PIN_1;
+ else
+ return PIN_SIG;
+ }
+ // Look to see if the driver is an inverter or constant
+ IdString drv_type = net->driver.cell->type;
+ if (drv_type == id_MISTRAL_NOT)
+ return PIN_INV;
+ else if (drv_type == id_GND)
+ return PIN_0;
+ else if (drv_type == id_VCC)
+ return PIN_1;
+ else
+ return PIN_SIG;
+ }
+
+ void uninvert_port(CellInfo *cell, IdString port)
+ {
+ // Rewire a port so it is driven by the input to an inverter
+ NetInfo *net = get_net_or_empty(cell, port);
+ NPNR_ASSERT(net != nullptr && net->driver.cell != nullptr && net->driver.cell->type == id_MISTRAL_NOT);
+ CellInfo *inv = net->driver.cell;
+ disconnect_port(ctx, cell, port);
+
+ NetInfo *inv_a = get_net_or_empty(inv, id_A);
+ if (inv_a != nullptr) {
+ connect_port(ctx, inv_a, cell, port);
+ }
+ }
+
+ void process_inv_constants(CellInfo *cell)
+ {
+ // TODO: we might need to create missing inputs here in some cases so we can tie them to the correct constant?
+ // Fold inverters and constants into a cell
+ for (auto &port : cell->ports) {
+ // Iterate over all inputs
+ if (port.second.type != PORT_IN)
+ continue;
+ IdString port_name = port.first;
+
+ CellPinState req_mux = get_pin_needed_muxval(cell, port_name);
+ if (req_mux == PIN_SIG) {
+ // No special setting required, ignore
+ continue;
+ }
+
+ CellPinStyle pin_style = ctx->get_cell_pin_style(cell, port_name);
+
+ if (req_mux == PIN_INV) {
+ // Pin is inverted. If there is a hard inverter; then use it
+ if (pin_style & PINOPT_INV) {
+ uninvert_port(cell, port_name);
+ cell->pin_data[port_name].state = PIN_INV;
+ }
+ } else if (req_mux == PIN_0 || req_mux == PIN_1) {
+ // Pin is tied to a constant
+ // If there is a hard constant option; use it
+ if ((pin_style & int(req_mux)) == req_mux) {
+ disconnect_port(ctx, cell, port_name);
+ cell->pin_data[port_name].state = req_mux;
+ } else {
+ disconnect_port(ctx, cell, port_name);
+ // There is no hard constant, we need to connect it to the relevant soft-constant net
+ connect_port(ctx, (req_mux == PIN_1) ? vcc_net : gnd_net, cell, port_name);
+ }
+ }
+ }
+ }
+
+ void trim_design()
+ {
+ // Remove unused inverters and high/low drivers
+ std::vector<IdString> trim_cells;
+ std::vector<IdString> trim_nets;
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type != id_MISTRAL_NOT && ci->type != id_GND && ci->type != id_VCC)
+ continue;
+ IdString port = (ci->type == id_MISTRAL_NOT) ? id_Q : id_Y;
+ NetInfo *out = get_net_or_empty(ci, port);
+ if (out == nullptr) {
+ trim_cells.push_back(ci->name);
+ continue;
+ }
+ if (!out->users.empty())
+ continue;
+
+ disconnect_port(ctx, ci, id_A);
+
+ trim_cells.push_back(ci->name);
+ trim_nets.push_back(out->name);
+ }
+
+ for (IdString rem_net : trim_nets)
+ ctx->nets.erase(rem_net);
+ for (IdString rem_cell : trim_cells)
+ ctx->cells.erase(rem_cell);
+ }
+
+ void pack_constants()
+ {
+ // Iterate through cells
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ // Skip certain cells at this point
+ if (ci->type != id_MISTRAL_NOT && ci->type != id_GND && ci->type != id_VCC)
+ process_inv_constants(cell.second);
+ }
+ // Special case - SDATA can only be trimmed if SLOAD is low
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type != id_MISTRAL_FF)
+ continue;
+ if (ci->get_pin_state(id_SLOAD) != PIN_0)
+ continue;
+ disconnect_port(ctx, ci, id_SDATA);
+ }
+ // Remove superfluous inverters and constant drivers
+ trim_design();
+ }
+
+ void prepare_io()
+ {
+ // Find the actual IO buffer corresponding to a port; and copy attributes across to it
+ // Note that this relies on Yosys to do IO buffer inference, to avoid tristate issues once we get to synthesised
+ // JSON. In all cases the nextpnr-inserted IO buffers are removed as redundant.
+ for (auto &port : sorted_ref(ctx->ports)) {
+ if (!ctx->cells.count(port.first))
+ log_error("Port '%s' doesn't seem to have a corresponding top level IO\n", ctx->nameOf(port.first));
+ CellInfo *ci = ctx->cells.at(port.first).get();
+
+ PortRef top_port;
+ top_port.cell = nullptr;
+ bool is_npnr_iob = false;
+
+ if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
+ // Might have an input buffer (IB etc) connected to it
+ is_npnr_iob = true;
+ NetInfo *o = get_net_or_empty(ci, id_O);
+ if (o == nullptr)
+ ;
+ else if (o->users.size() > 1)
+ log_error("Top level pin '%s' has multiple input buffers\n", ctx->nameOf(port.first));
+ else if (o->users.size() == 1)
+ top_port = o->users.at(0);
+ }
+ if (ci->type == ctx->id("$nextpnr_obuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
+ // Might have an output buffer (OB etc) connected to it
+ is_npnr_iob = true;
+ NetInfo *i = get_net_or_empty(ci, id_I);
+ if (i != nullptr && i->driver.cell != nullptr) {
+ if (top_port.cell != nullptr)
+ log_error("Top level pin '%s' has multiple input/output buffers\n", ctx->nameOf(port.first));
+ top_port = i->driver;
+ }
+ // Edge case of a bidirectional buffer driving an output pin
+ if (i->users.size() > 2) {
+ log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first));
+ } else if (i->users.size() == 2) {
+ if (top_port.cell != nullptr)
+ log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first));
+ for (auto &usr : i->users) {
+ if (usr.cell->type == ctx->id("$nextpnr_obuf") || usr.cell->type == ctx->id("$nextpnr_iobuf"))
+ continue;
+ top_port = usr;
+ break;
+ }
+ }
+ }
+ if (!is_npnr_iob)
+ log_error("Port '%s' doesn't seem to have a corresponding top level IO (internal cell type mismatch)\n",
+ ctx->nameOf(port.first));
+
+ if (top_port.cell == nullptr) {
+ log_info("Trimming port '%s' as it is unused.\n", ctx->nameOf(port.first));
+ } else {
+ // Copy attributes to real IO buffer
+ if (ctx->io_attr.count(port.first)) {
+ for (auto &kv : ctx->io_attr.at(port.first)) {
+ top_port.cell->attrs[kv.first] = kv.second;
+ }
+ }
+ // Make sure that top level net is set correctly
+ port.second.net = top_port.cell->ports.at(top_port.port).net;
+ }
+ // Now remove the nextpnr-inserted buffer
+ disconnect_port(ctx, ci, id_I);
+ disconnect_port(ctx, ci, id_O);
+ ctx->cells.erase(port.first);
+ }
+ }
+
+ void pack_io()
+ {
+ // Step 0: deal with top level inserted IO buffers
+ prepare_io();
+ // Stage 1: apply constraints
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ // Iterate through all IO buffer primitives
+ if (!ctx->is_io_cell(ci->type))
+ continue;
+ // We need all IO constrained at the moment, unconstrained IO are rare enough not to care
+ if (!ci->attrs.count(id_LOC))
+ log_error("Found unconstrained IO '%s', these are currently unsupported\n", ctx->nameOf(ci));
+ // Convert package pin constraint to bel constraint
+ std::string loc = ci->attrs.at(id_LOC).as_string();
+ if (loc.compare(0, 4, "PIN_") != 0)
+ log_error("Expecting PIN_-prefixed pin for IO '%s', got '%s'\n", ctx->nameOf(ci), loc.c_str());
+ auto pin_info = ctx->cyclonev->pin_find_name(loc.substr(4));
+ if (pin_info == nullptr)
+ log_error("IO '%s' is constrained to invalid pin '%s'\n", ctx->nameOf(ci), loc.c_str());
+ BelId bel = ctx->get_io_pin_bel(pin_info);
+
+ if (bel == BelId()) {
+ log_error("IO '%s' is constrained to pin %s which is not a supported IO pin.\n", ctx->nameOf(ci),
+ loc.c_str());
+ } else {
+ log_info("Constraining IO '%s' to pin %s (bel %s)\n", ctx->nameOf(ci), loc.c_str(),
+ ctx->nameOfBel(bel));
+ ctx->bindBel(bel, ci, STRENGTH_LOCKED);
+ }
+ }
+ }
+
+ void constrain_carries()
+ {
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type != id_MISTRAL_ALUT_ARITH)
+ continue;
+ const NetInfo *cin = get_net_or_empty(ci, id_CI);
+ if (cin != nullptr && cin->driver.cell != nullptr)
+ continue; // not the start of a chain
+ std::vector<CellInfo *> chain;
+ CellInfo *cursor = ci;
+ while (true) {
+ chain.push_back(cursor);
+ const NetInfo *co = get_net_or_empty(cursor, id_CO);
+ if (co == nullptr || co->users.empty())
+ break;
+ if (co->users.size() > 1)
+ log_error("Carry net %s has more than one sink!\n", ctx->nameOf(co));
+ auto &usr = co->users.at(0);
+ if (usr.port != id_CI)
+ log_error("Carry net %s drives port %s, expected CI\n", ctx->nameOf(co), ctx->nameOf(usr.port));
+ cursor = usr.cell;
+ }
+
+ chain.at(0)->constr_abs_z = true;
+ chain.at(0)->constr_z = 0;
+ chain.at(0)->cluster = chain.at(0)->name;
+
+ for (int i = 1; i < int(chain.size()); i++) {
+ chain.at(i)->constr_x = 0;
+ chain.at(i)->constr_y = -(i / 20);
+ // 2 COMB, 4 FF per ALM
+ chain.at(i)->constr_z = ((i / 2) % 10) * 6 + (i % 2);
+ chain.at(i)->constr_abs_z = true;
+ chain.at(i)->cluster = chain.at(0)->name;
+ chain.at(0)->constr_children.push_back(chain.at(i));
+ }
+
+ if (ctx->debug) {
+ log_info("Chain: \n");
+ for (int i = 0; i < int(chain.size()); i++) {
+ auto &c = chain.at(i);
+ log_info(" i=%d cell=%s dy=%d z=%d ci=%s co=%s\n", i, ctx->nameOf(c), c->constr_y, c->constr_z,
+ ctx->nameOf(get_net_or_empty(c, id_CI)), ctx->nameOf(get_net_or_empty(c, id_CO)));
+ }
+ }
+ }
+ // Check we reached all the cells in the above pass
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type != id_MISTRAL_ALUT_ARITH)
+ continue;
+ if (ci->cluster == ClusterId())
+ log_error("Failed to include arith cell '%s' in any chain (CI=%s)\n", ctx->nameOf(ci),
+ ctx->nameOf(get_net_or_empty(ci, id_CI)));
+ }
+ }
+
+ void run()
+ {
+ init_constant_nets();
+ pack_constants();
+ pack_io();
+ constrain_carries();
+ }
+};
+}; // namespace
+
+bool Arch::pack()
+{
+ MistralPacker packer(getCtx());
+ packer.run();
+
+ assignArchInfo();
+
+ return true;
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/mistral/pins.cc b/mistral/pins.cc
new file mode 100644
index 00000000..c3637115
--- /dev/null
+++ b/mistral/pins.cc
@@ -0,0 +1,67 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 gatecat <gatecat@ds0.me>
+ *
+ * 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 "log.h"
+#include "nextpnr.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+const std::unordered_map<IdString, Arch::CellPinsData> Arch::cell_pins_db = {
+ // For combinational cells, inversion and tieing can be implemented by manipulating the LUT function
+ {id_MISTRAL_ALUT2, {{{}, PINSTYLE_COMB}}},
+ {id_MISTRAL_ALUT3, {{{}, PINSTYLE_COMB}}},
+ {id_MISTRAL_ALUT4, {{{}, PINSTYLE_COMB}}},
+ {id_MISTRAL_ALUT5, {{{}, PINSTYLE_COMB}}},
+ {id_MISTRAL_ALUT6, {{{}, PINSTYLE_COMB}}},
+ {id_MISTRAL_ALUT_ARITH,
+ {// Leave carry chain alone, other than disconnecting a ground constant
+ {id_CI, PINSTYLE_CARRY},
+ {{}, PINSTYLE_COMB}}},
+ {id_MISTRAL_FF,
+ {
+ {id_CLK, PINSTYLE_CLK},
+ {id_ENA, PINSTYLE_CE},
+ {id_ACLR, PINSTYLE_RST},
+ {id_SCLR, PINSTYLE_RST},
+ {id_SLOAD, PINSTYLE_RST},
+ {id_SDATA, PINSTYLE_DEDI},
+ {id_DATAIN, PINSTYLE_INP},
+ }},
+};
+
+CellPinStyle Arch::get_cell_pin_style(const CellInfo *cell, IdString port) const
+{
+ // Look up the pin style in the cell database
+ auto fnd_cell = cell_pins_db.find(cell->type);
+ if (fnd_cell == cell_pins_db.end())
+ return PINSTYLE_NONE;
+ auto fnd_port = fnd_cell->second.find(port);
+ if (fnd_port != fnd_cell->second.end())
+ return fnd_port->second;
+ // If there isn't an exact port match, then the empty IdString
+ // represents a wildcard default match
+ auto fnd_default = fnd_cell->second.find({});
+ if (fnd_default != fnd_cell->second.end())
+ return fnd_default->second;
+
+ return PINSTYLE_NONE;
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/mistral/qsf.cc b/mistral/qsf.cc
new file mode 100644
index 00000000..9a128595
--- /dev/null
+++ b/mistral/qsf.cc
@@ -0,0 +1,281 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2021 gatecat <gatecat@ds0.me>
+ *
+ * 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 "log.h"
+#include "nextpnr.h"
+#include "util.h"
+
+#include <iterator>
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace {
+
+struct QsfOption
+{
+ std::string name; // name, excluding the initial '-'
+ int arg_count; // number of arguments that follow the option
+ bool required; // error out if this option isn't passed
+};
+
+typedef std::unordered_map<std::string, std::vector<std::string>> option_map_t;
+
+struct QsfCommand
+{
+ std::string name; // name of the command
+ std::vector<QsfOption> options; // list of "-options"
+ int pos_arg_count; // number of positional arguments expected to follow the command, -1 for any
+ std::function<void(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args)> func;
+};
+
+void set_location_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args)
+{
+ ctx->io_attr[ctx->id(options.at("to").at(0))][id_LOC] = pos_args.at(0);
+}
+
+void set_instance_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args)
+{
+ ctx->io_attr[ctx->id(options.at("to").at(0))][ctx->id(options.at("name").at(0))] = pos_args.at(0);
+}
+
+void set_global_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args)
+{
+ // TODO
+}
+
+static const std::vector<QsfCommand> commands = {
+ {"set_location_assignment", {{"to", 1, true}}, 1, set_location_assignment_cmd},
+ {"set_instance_assignment",
+ {{"to", 1, true}, {"name", 1, true}, {"section_id", 1, false}},
+ 1,
+ set_instance_assignment_cmd},
+ {"set_global_assignment",
+ {{"name", 1, true}, {"section_id", 1, false}, {"rise", 0, false}, {"fall", 0, false}},
+ 1,
+ set_global_assignment_cmd},
+};
+
+struct QsfParser
+{
+ std::string buf;
+ int pos = 0;
+ int lineno = 0;
+ Context *ctx;
+
+ QsfParser(const std::string &buf, Context *ctx) : buf(buf), ctx(ctx){};
+
+ inline bool eof() const { return pos == int(buf.size()); }
+
+ inline char peek() const { return buf.at(pos); }
+
+ inline char get()
+ {
+ char c = buf.at(pos++);
+ if (c == '\n')
+ ++lineno;
+ return c;
+ }
+
+ std::string get(int n)
+ {
+ std::string s = buf.substr(pos, n);
+ pos += n;
+ return s;
+ }
+
+ // If next char matches c, take it from the stream and return true
+ bool check_get(char c)
+ {
+ if (peek() == c) {
+ get();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // If next char matches any in chars, take it from the stream and return true
+ bool check_get_any(const std::string &chrs)
+ {
+ char c = peek();
+ if (chrs.find(c) != std::string::npos) {
+ get();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ inline void skip_blank(bool nl = false)
+ {
+ while (!eof() && check_get_any(nl ? " \t\n\r" : " \t"))
+ ;
+ }
+
+ // Return true if end of line (or file)
+ inline bool skip_check_eol()
+ {
+ skip_blank(false);
+ if (eof())
+ return true;
+ char c = peek();
+ // Comments count as end of line
+ if (c == '#') {
+ get();
+ while (!eof() && peek() != '\n' && peek() != '\r')
+ get();
+ return true;
+ }
+ if (c == ';') {
+ // Forced end of line
+ get();
+ return true;
+ }
+ return (c == '\n' || c == '\r');
+ }
+
+ // We need to distinguish between quoted and unquoted strings, the former don't count as options
+ struct StringVal
+ {
+ std::string str;
+ bool is_quoted = false;
+ };
+
+ inline StringVal get_str()
+ {
+ StringVal s;
+ skip_blank(false);
+ if (eof())
+ return {"", false};
+
+ bool in_quotes = false, in_braces = false, escaped = false;
+
+ char c = get();
+
+ if (c == '"') {
+ in_quotes = true;
+ s.is_quoted = true;
+ } else if (c == '{') {
+ in_braces = true;
+ s.is_quoted = true;
+ } else {
+ s.str += c;
+ }
+
+ while (!eof()) {
+ char c = peek();
+ if (!in_quotes && !in_braces && !escaped && (std::isblank(c) || c == '\n' || c == '\r')) {
+ break;
+ }
+ get();
+ if (escaped) {
+ s.str += c;
+ escaped = false;
+ } else if ((in_quotes && c == '"') || (in_braces && c == '}')) {
+ break;
+ } else if (c == '\\') {
+ escaped = true;
+ } else {
+ s.str += c;
+ }
+ }
+
+ return s;
+ }
+
+ std::vector<StringVal> get_arguments()
+ {
+ std::vector<StringVal> args;
+ while (!skip_check_eol()) {
+ args.push_back(get_str());
+ }
+ skip_blank(true);
+ return args;
+ }
+
+ void evaluate(const std::vector<StringVal> &args)
+ {
+ if (args.empty())
+ return;
+ auto cmd_name = args.at(0).str;
+ auto fnd_cmd =
+ std::find_if(commands.begin(), commands.end(), [&](const QsfCommand &c) { return c.name == cmd_name; });
+ if (fnd_cmd == commands.end()) {
+ log_warning("Ignoring unknown command '%s' (line %d)\n", cmd_name.c_str(), lineno);
+ return;
+ }
+ option_map_t opt;
+ std::vector<std::string> pos_args;
+ for (size_t i = 1; i < args.size(); i++) {
+ auto arg = args.at(i);
+ if (arg.str.at(0) == '-' && !arg.is_quoted) {
+ for (auto &opt_data : fnd_cmd->options) {
+ if (arg.str.compare(1, std::string::npos, opt_data.name) != 0)
+ continue;
+ opt[opt_data.name]; // create empty entry, even if 0 arguments
+ for (int j = 0; j < opt_data.arg_count; j++) {
+ ++i;
+ if (i >= args.size())
+ log_error("Unexpected end of argument list to option '%s' (line %d)\n", arg.str.c_str(),
+ lineno);
+ opt[opt_data.name].push_back(args.at(i).str);
+ }
+ goto done;
+ }
+ log_error("Unknown option '%s' to command '%s' (line %d)\n", arg.str.c_str(), cmd_name.c_str(), lineno);
+ done:;
+ } else {
+ // positional argument
+ pos_args.push_back(arg.str);
+ }
+ }
+ // Check positional argument count
+ if (int(pos_args.size()) != fnd_cmd->pos_arg_count && fnd_cmd->pos_arg_count != -1) {
+ log_error("Expected %d positional arguments to command '%s', got %d (line %d)\n", fnd_cmd->pos_arg_count,
+ cmd_name.c_str(), int(pos_args.size()), lineno);
+ }
+ // Check required options
+ for (auto &opt_data : fnd_cmd->options) {
+ if (opt_data.required && !opt.count(opt_data.name))
+ log_error("Missing required option '%s' to command '%s' (line %d)\n", opt_data.name.c_str(),
+ cmd_name.c_str(), lineno);
+ }
+ // Execute
+ fnd_cmd->func(ctx, opt, pos_args);
+ }
+
+ void operator()()
+ {
+ while (!eof()) {
+ skip_blank(true);
+ auto args = get_arguments();
+ evaluate(args);
+ }
+ }
+};
+
+}; // namespace
+
+void Arch::read_qsf(std::istream &in)
+{
+ std::string buf(std::istreambuf_iterator<char>(in), {});
+ QsfParser(buf, getCtx())();
+}
+
+NEXTPNR_NAMESPACE_END