From 879ac39e53c76558766460cfa948d97227119f37 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 8 May 2021 11:00:58 +0100 Subject: mistral: Renamed arch from cyclonev Signed-off-by: gatecat --- CMakeLists.txt | 4 +- cyclonev/arch.cc | 339 ------------------------------------------ cyclonev/arch.h | 405 -------------------------------------------------- cyclonev/archdefs.h | 225 ---------------------------- cyclonev/constids.inc | 68 --------- cyclonev/family.cmake | 11 -- cyclonev/io.cc | 53 ------- cyclonev/lab.cc | 399 ------------------------------------------------- cyclonev/main.cc | 86 ----------- mistral/arch.cc | 339 ++++++++++++++++++++++++++++++++++++++++++ mistral/arch.h | 405 ++++++++++++++++++++++++++++++++++++++++++++++++++ mistral/archdefs.h | 225 ++++++++++++++++++++++++++++ mistral/constids.inc | 68 +++++++++ mistral/family.cmake | 11 ++ mistral/io.cc | 53 +++++++ mistral/lab.cc | 399 +++++++++++++++++++++++++++++++++++++++++++++++++ mistral/main.cc | 86 +++++++++++ 17 files changed, 1588 insertions(+), 1588 deletions(-) delete mode 100644 cyclonev/arch.cc delete mode 100644 cyclonev/arch.h delete mode 100644 cyclonev/archdefs.h delete mode 100644 cyclonev/constids.inc delete mode 100644 cyclonev/family.cmake delete mode 100644 cyclonev/io.cc delete mode 100644 cyclonev/lab.cc delete mode 100644 cyclonev/main.cc create mode 100644 mistral/arch.cc create mode 100644 mistral/arch.h create mode 100644 mistral/archdefs.h create mode 100644 mistral/constids.inc create mode 100644 mistral/family.cmake create mode 100644 mistral/io.cc create mode 100644 mistral/lab.cc create mode 100644 mistral/main.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d007ff7..233d5797 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,9 +95,9 @@ endif() set(PROGRAM_PREFIX "" CACHE STRING "Name prefix for executables") # List of families to build -set(FAMILIES generic ice40 ecp5 nexus gowin fpga_interchange machxo2 cyclonev) +set(FAMILIES generic ice40 ecp5 nexus gowin fpga_interchange machxo2 mistral) set(STABLE_FAMILIES generic ice40 ecp5) -set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange machxo2 cyclonev) +set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange machxo2 mistral) set(ARCH "" CACHE STRING "Architecture family for nextpnr build") set_property(CACHE ARCH PROPERTY STRINGS ${FAMILIES}) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc deleted file mode 100644 index fd2f345d..00000000 --- a/cyclonev/arch.cc +++ /dev/null @@ -1,339 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021 Lofty - * - * 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 - -#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 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 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 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 Arch::getBelsByTile(int x, int y) const -{ - // This should probably be redesigned, but it's a hack. - std::vector 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 Arch::getBelPins(BelId bel) const -{ - std::vector 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 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 Arch::availablePlacers = {"sa", -#ifdef WITH_HEAP - "heap" -#endif -}; - -const std::string Arch::defaultRouter = "router1"; -const std::vector Arch::availableRouters = {"router1", "router2"}; - -NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/cyclonev/arch.h b/cyclonev/arch.h deleted file mode 100644 index ff006881..00000000 --- a/cyclonev/arch.h +++ /dev/null @@ -1,405 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021 Lofty - * - * 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 -#include - -#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 lut_bels; - std::array ff_bels; - // TODO: ALM configuration (L5/L6 mode, LUT input permutation, etc) -}; - -struct LABInfo -{ - std::array alms; - // Control set wires - std::array clk_wires, ena_wires; - std::array 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 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 wires_downhill; - std::vector wires_uphill; - - std::vector 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::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 &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::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 &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 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>; - -struct ArchRanges : BaseArchRanges -{ - using ArchArgsT = ArchArgs; - // Bels - using AllBelsRangeT = const std::vector &; - using TileBelsRangeT = std::vector; - using BelPinsRangeT = std::vector; - // Wires - using AllWiresRangeT = AllWireRange; - using DownhillPipRangeT = UpDownhillPipRange; - using UphillPipRangeT = UpDownhillPipRange; - using WireBelPinRangeT = const std::vector &; - // Pips - using AllPipsRangeT = AllPipRange; -}; - -struct Arch : BaseArch -{ - 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 &getBels() const override { return all_bels; } - std::vector 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 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 &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 availablePlacers; - static const std::string defaultRouter; - static const std::vector availableRouters; - - std::unordered_map wires; - - // List of LABs - std::vector labs; - - // WIP to link without failure - std::vector empty_belpin_list; - - // Conversion between numbers and rnode types and IdString, for fast wire name implementation - std::vector int2id; - std::unordered_map id2int; - - std::vector rn_t2id; - std::unordered_map id2rn_t; - - // This structure is only used for nextpnr-created wires - std::unordered_map npnr_wirebyname; - - std::vector> bels_by_tile; - std::vector 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 comb_pinmap; -}; - -NEXTPNR_NAMESPACE_END - -#endif \ No newline at end of file diff --git a/cyclonev/archdefs.h b/cyclonev/archdefs.h deleted file mode 100644 index 0f8f5a12..00000000 --- a/cyclonev/archdefs.h +++ /dev/null @@ -1,225 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021 Lofty - -#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::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 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 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 pin_data; -}; - -NEXTPNR_NAMESPACE_END - -namespace std { -template <> struct hash -{ - std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept - { - return hash()((static_cast(bel.pos) << 16) | bel.z); - } -}; - -template <> struct hash -{ - std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept - { - return hash()(wire.node); - } -}; - -template <> struct hash -{ - std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept - { - return hash()((uint64_t(pip.dst) << 32) | pip.src); - } -}; - -} // namespace std - -#endif \ No newline at end of file diff --git a/cyclonev/constids.inc b/cyclonev/constids.inc deleted file mode 100644 index c4af45e6..00000000 --- a/cyclonev/constids.inc +++ /dev/null @@ -1,68 +0,0 @@ -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/cyclonev/family.cmake b/cyclonev/family.cmake deleted file mode 100644 index 92ec7d12..00000000 --- a/cyclonev/family.cmake +++ /dev/null @@ -1,11 +0,0 @@ -set(MISTRAL_ROOT "" CACHE STRING "Mistral install path") - -aux_source_directory(${MISTRAL_ROOT}/lib MISTRAL_FILES) -add_library(mistral STATIC ${MISTRAL_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/cyclonev/io.cc b/cyclonev/io.cc deleted file mode 100644 index f2517e5d..00000000 --- a/cyclonev/io.cc +++ /dev/null @@ -1,53 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021 gatecat - * - * 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/cyclonev/lab.cc b/cyclonev/lab.cc deleted file mode 100644 index 1f117106..00000000 --- a/cyclonev/lab.cc +++ /dev/null @@ -1,399 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021 gatecat - * - * 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 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 luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])}; - std::array 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 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/cyclonev/main.cc b/cyclonev/main.cc deleted file mode 100644 index 702b4b7e..00000000 --- a/cyclonev/main.cc +++ /dev/null @@ -1,86 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021 gatecat - * - * 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 -#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 createContext(std::unordered_map &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(), "path to mistral root"); - specific.add_options()("device", po::value(), "device name (e.g. 5CSEBA6U23I7)"); - return specific; -} - -void MistralCommandHandler::customBitstream(Context *ctx) -{ - // TODO: rbf gen via mistral -} - -std::unique_ptr MistralCommandHandler::createContext(std::unordered_map &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(); - chipArgs.device = vm["device"].as(); - auto ctx = std::unique_ptr(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 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 + * + * 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 + +#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 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 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 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 Arch::getBelsByTile(int x, int y) const +{ + // This should probably be redesigned, but it's a hack. + std::vector 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 Arch::getBelPins(BelId bel) const +{ + std::vector 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 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 Arch::availablePlacers = {"sa", +#ifdef WITH_HEAP + "heap" +#endif +}; + +const std::string Arch::defaultRouter = "router1"; +const std::vector 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 + * + * 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 +#include + +#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 lut_bels; + std::array ff_bels; + // TODO: ALM configuration (L5/L6 mode, LUT input permutation, etc) +}; + +struct LABInfo +{ + std::array alms; + // Control set wires + std::array clk_wires, ena_wires; + std::array 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 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 wires_downhill; + std::vector wires_uphill; + + std::vector 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::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 &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::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 &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 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>; + +struct ArchRanges : BaseArchRanges +{ + using ArchArgsT = ArchArgs; + // Bels + using AllBelsRangeT = const std::vector &; + using TileBelsRangeT = std::vector; + using BelPinsRangeT = std::vector; + // Wires + using AllWiresRangeT = AllWireRange; + using DownhillPipRangeT = UpDownhillPipRange; + using UphillPipRangeT = UpDownhillPipRange; + using WireBelPinRangeT = const std::vector &; + // Pips + using AllPipsRangeT = AllPipRange; +}; + +struct Arch : BaseArch +{ + 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 &getBels() const override { return all_bels; } + std::vector 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 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 &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 availablePlacers; + static const std::string defaultRouter; + static const std::vector availableRouters; + + std::unordered_map wires; + + // List of LABs + std::vector labs; + + // WIP to link without failure + std::vector empty_belpin_list; + + // Conversion between numbers and rnode types and IdString, for fast wire name implementation + std::vector int2id; + std::unordered_map id2int; + + std::vector rn_t2id; + std::unordered_map id2rn_t; + + // This structure is only used for nextpnr-created wires + std::unordered_map npnr_wirebyname; + + std::vector> bels_by_tile; + std::vector 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 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 + +#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::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 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 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 pin_data; +}; + +NEXTPNR_NAMESPACE_END + +namespace std { +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept + { + return hash()((static_cast(bel.pos) << 16) | bel.z); + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept + { + return hash()(wire.node); + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept + { + return hash()((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 + * + * 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 + * + * 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 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 luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])}; + std::array 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 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 + * + * 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 +#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 createContext(std::unordered_map &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(), "path to mistral root"); + specific.add_options()("device", po::value(), "device name (e.g. 5CSEBA6U23I7)"); + return specific; +} + +void MistralCommandHandler::customBitstream(Context *ctx) +{ + // TODO: rbf gen via mistral +} + +std::unique_ptr MistralCommandHandler::createContext(std::unordered_map &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(); + chipArgs.device = vm["device"].as(); + auto ctx = std::unique_ptr(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 -- cgit v1.2.3