diff options
Diffstat (limited to 'mistral')
-rw-r--r-- | mistral/arch.cc | 339 | ||||
-rw-r--r-- | mistral/arch.h | 405 | ||||
-rw-r--r-- | mistral/archdefs.h | 225 | ||||
-rw-r--r-- | mistral/constids.inc | 68 | ||||
-rw-r--r-- | mistral/family.cmake | 11 | ||||
-rw-r--r-- | mistral/io.cc | 53 | ||||
-rw-r--r-- | mistral/lab.cc | 399 | ||||
-rw-r--r-- | mistral/main.cc | 86 |
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 |