aboutsummaryrefslogtreecommitdiffstats
path: root/mistral
diff options
context:
space:
mode:
Diffstat (limited to 'mistral')
-rw-r--r--mistral/arch.cc339
-rw-r--r--mistral/arch.h405
-rw-r--r--mistral/archdefs.h225
-rw-r--r--mistral/constids.inc68
-rw-r--r--mistral/family.cmake11
-rw-r--r--mistral/io.cc53
-rw-r--r--mistral/lab.cc399
-rw-r--r--mistral/main.cc86
8 files changed, 1586 insertions, 0 deletions
diff --git a/mistral/arch.cc b/mistral/arch.cc
new file mode 100644
index 00000000..fd2f345d
--- /dev/null
+++ b/mistral/arch.cc
@@ -0,0 +1,339 @@
+/*
+ * 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 "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;
+ case CycloneV::block_type_t::GPIO:
+ create_gpio(x, y);
+ break;
+ default:
+ continue;
+ }
+ }
+ }
+ }
+
+ // 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);
+ // Incremental validity update
+ if (data.type == id_MISTRAL_COMB) {
+ return is_alm_legal(data.lab_data.lab, data.lab_data.alm);
+ } else if (data.type == id_MISTRAL_FF) {
+ return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && is_lab_ctrlset_legal(data.lab_data.lab);
+ }
+ return true;
+}
+
+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
+ 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
+ return cell_type;
+}
+
+bool Arch::pack() { return true; }
+bool Arch::place() { return true; }
+bool Arch::route() { return true; }
+
+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;
+ }
+}
+
+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
+ }
+}
+
+#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 = "router1";
+const std::vector<std::string> Arch::availableRouters = {"router1", "router2"};
+
+NEXTPNR_NAMESPACE_END \ No newline at end of file
diff --git a/mistral/arch.h b/mistral/arch.h
new file mode 100644
index 00000000..ff006881
--- /dev/null
+++ b/mistral/arch.h
@@ -0,0 +1,405 @@
+/*
+ * 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
+{
+ // Pointers to bels
+ std::array<BelId, 2> lut_bels;
+ std::array<BelId, 4> ff_bels;
+ // TODO: ALM configuration (L5/L6 mode, LUT input permutation, etc)
+};
+
+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)
+};
+
+struct PinInfo
+{
+ WireId wire;
+ PortType dir;
+};
+
+struct BelInfo
+{
+ IdString name;
+ IdString type;
+ IdString bucket;
+ // 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;
+};
+
+// 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>;
+ // Wires
+ using AllWiresRangeT = AllWireRange;
+ using DownhillPipRangeT = UpDownhillPipRange;
+ using UphillPipRangeT = UpDownhillPipRange;
+ using WireBelPinRangeT = const std::vector<BelPin> &;
+ // Pips
+ using AllPipsRangeT = AllPipRange;
+};
+
+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;
+
+ // -------------------------------------------------
+
+ 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); }
+
+ // -------------------------------------------------
+
+ PipId getPipByName(IdStringList name) const override;
+ AllPipRange getPips() const override { return AllPipRange(wires); }
+ Loc getPipLocation(PipId pip) const override { return Loc(0, 0, 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(0); }
+ 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);
+ }
+
+ // -------------------------------------------------
+
+ delay_t estimateDelay(WireId src, WireId dst) const override { return 100; };
+ delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override { return 100; };
+ 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 { return ArcBounds(); }
+
+ // -------------------------------------------------
+
+ bool isValidBelForCellType(IdString cell_type, BelId bel) const override;
+ BelBucketId getBelBucketForCellType(IdString cell_type) const 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
+
+ // -------------------------------------------------
+
+ 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
+
+ void assign_comb_info(CellInfo *cell) const; // lab.cc
+ void assign_ff_info(CellInfo *cell) const; // lab.cc
+
+ // -------------------------------------------------
+
+ bool is_io_cell(IdString cell_type) const; // io.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;
+};
+
+NEXTPNR_NAMESPACE_END
+
+#endif \ No newline at end of file
diff --git a/mistral/archdefs.h b/mistral/archdefs.h
new file mode 100644
index 00000000..0f8f5a12
--- /dev/null
+++ b/mistral/archdefs.h
@@ -0,0 +1,225 @@
+/*
+ * 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
+{
+};
+
+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;
+
+ bool is_carry, is_shared, is_extended;
+ } combInfo;
+ struct
+ {
+ FFControlSet ctrlset;
+ const NetInfo *sdata, *datain;
+ } ffInfo;
+ };
+
+ std::unordered_map<IdString, ArchPinInfo> pin_data;
+};
+
+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 \ No newline at end of file
diff --git a/mistral/constids.inc b/mistral/constids.inc
new file mode 100644
index 00000000..c4af45e6
--- /dev/null
+++ b/mistral/constids.inc
@@ -0,0 +1,68 @@
+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(CIN)
+X(SHAREIN)
+X(COUT)
+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(D0)
+X(D1)
+X(CI)
+X(CO)
+X(SO)
+
+X(WIRE) \ No newline at end of file
diff --git a/mistral/family.cmake b/mistral/family.cmake
new file mode 100644
index 00000000..441d81db
--- /dev/null
+++ b/mistral/family.cmake
@@ -0,0 +1,11 @@
+set(MISTRAL_ROOT "" CACHE STRING "Mistral install path")
+
+aux_source_directory(${MISTRAL_ROOT}/lib MISTRAL_LIB_FILES)
+add_library(mistral STATIC ${MISTRAL_LIB_FILES})
+
+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})
+endforeach()
diff --git a/mistral/io.cc b/mistral/io.cc
new file mode 100644
index 00000000..f2517e5d
--- /dev/null
+++ b/mistral/io.cc
@@ -0,0 +1,53 @@
+/*
+ * 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));
+ }
+}
+
+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;
+ }
+}
+
+NEXTPNR_NAMESPACE_END \ No newline at end of file
diff --git a/mistral/lab.cc b/mistral/lab.cc
new file mode 100644
index 00000000..1f117106
--- /dev/null
+++ b/mistral/lab.cc
@@ -0,0 +1,399 @@
+/*
+ * 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
+
+// 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);
+ // 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) {
+ if (y == 0) {
+ // Base case
+ carry_in = arch->add_wire(x, y, id_CARRY_START);
+ share_in = arch->add_wire(x, y, id_CARRY_START);
+ } else {
+ // Output of last tile
+ carry_in = arch->add_wire(x, y - 1, id_COUT);
+ share_in = arch->add_wire(x, y - 1, id_SHAREOUT);
+ }
+ } 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_COUT);
+ 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_CIN, PORT_IN, carry_in);
+ arch->add_bel_pin(bel, id_SHAREIN, PORT_IN, share_in);
+ arch->add_bel_pin(bel, id_COUT, PORT_OUT, carry_out);
+ arch->add_bel_pin(bel, id_SHAREOUT, PORT_OUT, share_out);
+ // Combinational output
+ WireId comb_out = arch->add_wire(x, y, arch->id(stringf("COMBOUT[%d]", z * 2 + i)));
+ arch->add_bel_pin(bel, id_COMBOUT, PORT_OUT, comb_out);
+ // Assign indexing
+ lab.alms.at(z).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
+ std::array<WireId, 2> sel_clk, sel_ena, sel_aclr, sel_ef;
+ for (int i = 0; i < 2; i++) {
+ // Wires
+ sel_clk[i] = arch->add_wire(x, y, arch->id(stringf("CLK%c[%d]", i ? 'B' : 'T', z)));
+ sel_ena[i] = arch->add_wire(x, y, arch->id(stringf("ENA%c[%d]", i ? 'B' : 'T', z)));
+ sel_aclr[i] = arch->add_wire(x, y, arch->id(stringf("ACLR%c[%d]", i ? 'B' : 'T', z)));
+ 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], sel_clk[i]);
+ arch->add_pip(lab.ena_wires[j], sel_ena[i]);
+ if (j < 2)
+ arch->add_pip(lab.aclr_wires[j], sel_aclr[i]);
+ }
+ // E/F pips
+ arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::E1 : CycloneV::E0), sel_ef[i]);
+ arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::F1 : CycloneV::F0), 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*
+ WireId comb_out = arch->add_wire(x, y, arch->id(stringf("COMBOUT[%d]", (z * 2) + (i / 2))));
+ WireId ff_in = arch->add_wire(x, y, arch->id(stringf("FFIN[%d]", (z * 4) + i)));
+ arch->add_pip(comb_out, ff_in);
+ arch->add_pip(sel_ef[i / 2], ff_in);
+ // 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, sel_clk[i / 2]);
+ arch->add_bel_pin(bel, id_ENA, PORT_IN, sel_ena[i / 2]);
+ arch->add_bel_pin(bel, id_ACLR, PORT_IN, 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, ff_in);
+ arch->add_bel_pin(bel, id_SDATA, PORT_IN, sel_ef[i / 2]);
+
+ // FF output
+ WireId ff_out = arch->add_wire(x, y, arch->id(stringf("FFOUT[%d]", (z * 4) + i)));
+ arch->add_bel_pin(bel, id_Q, PORT_OUT, ff_out);
+ // Output mux (*DFF*)
+ WireId out = arch->get_port(CycloneV::LAB, x, y, z, outputs[i]);
+ arch->add_pip(ff_out, out);
+ arch->add_pip(comb_out, 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(ff_out, l_out);
+ arch->add_pip(comb_out, 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 CellInfo *cell, IdString port)
+{
+ ControlSig result;
+ result.net = get_net_or_empty(cell, port);
+ 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;
+
+ 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
+ int i = 0;
+ for (auto pin : {id_A, id_B, id_C, id_D0, id_D1}) {
+ cell->combInfo.lut_in[i++] = get_net_or_empty(cell, pin);
+ }
+ cell->combInfo.comb_out = get_net_or_empty(cell, id_SO);
+ } 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(cell, id_CLK);
+ cell->ffInfo.ctrlset.ena = get_ctrlsig(cell, id_ENA);
+ cell->ffInfo.ctrlset.aclr = get_ctrlsig(cell, id_ACLR);
+ cell->ffInfo.ctrlset.sclr = get_ctrlsig(cell, id_SCLR);
+ cell->ffInfo.ctrlset.sload = get_ctrlsig(cell, id_SLOAD);
+ 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;
+ }
+
+ // 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] && (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
+ bool ef_available = (!luts[i] || luts[i]->combInfo.used_lut_input_count <= 3);
+ // 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 (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;
+}
+
+bool Arch::is_lab_ctrlset_legal(uint32_t lab) const
+{
+ // TODO
+ return true;
+}
+
+// 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},
+};
+
+NEXTPNR_NAMESPACE_END \ No newline at end of file
diff --git a/mistral/main.cc b/mistral/main.cc
new file mode 100644
index 00000000..702b4b7e
--- /dev/null
+++ b/mistral/main.cc
@@ -0,0 +1,86 @@
+/*
+ * 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)");
+ return specific;
+}
+
+void MistralCommandHandler::customBitstream(Context *ctx)
+{
+ // TODO: rbf gen via mistral
+}
+
+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)
+{
+ // TODO: qsf parsing
+}
+
+int main(int argc, char *argv[])
+{
+ MistralCommandHandler handler(argc, argv);
+ return handler.exec();
+}
+
+#endif