From 6ffbb9ed87ae11ccf3a1f1053162f668bda8e135 Mon Sep 17 00:00:00 2001 From: Dan Ravensloft Date: Fri, 12 Jun 2020 22:09:46 +0100 Subject: cyclonev: basic platform --- CMakeLists.txt | 2 +- cyclonev/arch.cc | 18 +++ cyclonev/arch.h | 373 ++++++++++++++++++++++++++++++++++++++++++++++++++ cyclonev/archdefs.h | 187 +++++++++++++++++++++++++ cyclonev/family.cmake | 0 5 files changed, 579 insertions(+), 1 deletion(-) create mode 100644 cyclonev/arch.cc create mode 100644 cyclonev/arch.h create mode 100644 cyclonev/archdefs.h create mode 100644 cyclonev/family.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 8291a21f..cc3e100f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,7 +97,7 @@ set(PROGRAM_PREFIX "" CACHE STRING "Name prefix for executables") # List of families to build set(FAMILIES generic ice40 ecp5 nexus gowin fpga_interchange machxo2) set(STABLE_FAMILIES generic ice40 ecp5) -set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange machxo2) +set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange machxo2 cyclonev) 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 new file mode 100644 index 00000000..e0721791 --- /dev/null +++ b/cyclonev/arch.cc @@ -0,0 +1,18 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 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 NEXTPNR_H +#error Include "arch.h" via "nextpnr.h" only. +#endif + +NEXTPNR_NAMESPACE_BEGIN + +struct BelIterator +{ + int cursor; + + BelIterator operator++() + { + cursor++; + return *this; + } + BelIterator operator++(int) + { + BelIterator prior(*this); + cursor++; + return prior; + } + + bool operator!=(const BelIterator &other) const { return cursor != other.cursor; } + + bool operator==(const BelIterator &other) const { return cursor == other.cursor; } + + BelId operator*() const + { + BelId ret; + ret.index = cursor; + return ret; + } +}; + +struct BelRange +{ + BelIterator b, e; + BelIterator begin() const { return b; } + BelIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct BelPinIterator +{ + const /* something */ int *ptr = nullptr; + + void operator++() { ptr++; } + bool operator!=(const BelPinIterator &other) const { return ptr != other.ptr; } + + BelPin operator*() const + { + BelPin ret; + return ret; + } +}; + +struct BelPinRange +{ + BelPinIterator b, e; + BelPinIterator begin() const { return b; } + BelPinIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct WireIterator +{ + int cursor = -1; + + void operator++() { cursor++; } + bool operator!=(const WireIterator &other) const { return cursor != other.cursor; } + + WireId operator*() const + { + WireId ret; + ret.index = cursor; + return ret; + } +}; + +struct WireRange +{ + WireIterator b, e; + WireIterator begin() const { return b; } + WireIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct AllPipIterator +{ + int cursor = -1; + + void operator++() { cursor++; } + bool operator!=(const AllPipIterator &other) const { return cursor != other.cursor; } + + PipId operator*() const + { + PipId ret; + ret.index = cursor; + return ret; + } +}; + +struct AllPipRange +{ + AllPipIterator b, e; + AllPipIterator begin() const { return b; } + AllPipIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct PipIterator +{ + const int *cursor = nullptr; + + void operator++() { cursor++; } + bool operator!=(const PipIterator &other) const { return cursor != other.cursor; } + + PipId operator*() const + { + PipId ret; + ret.index = *cursor; + return ret; + } +}; + +struct PipRange +{ + PipIterator b, e; + PipIterator begin() const { return b; } + PipIterator end() const { return e; } +}; + +struct ArchArgs +{ +}; + +struct Arch : BaseCtx +{ + ArchArgs args; + Arch(ArchArgs args); + + std::string getChipName() const; + + IdString archId() const { return id("cyclonev"); } + ArchArgs archArgs() const; + IdString archArgsToId(ArchArgs args) const; + + // ------------------------------------------------- + + int getGridDimX() const; + int getGridDimY() const; + int getTileBelDimZ(int, int) const; + int getTilePipDimZ(int, int) const; + + // ------------------------------------------------- + + BelId getBelByName(IdString name) const; + + IdString getBelName(BelId bel) const; + + uint32_t getBelChecksum(BelId bel) const; + + void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength); + + void unbindBel(BelId bel); + + bool checkBelAvail(BelId bel) const; + + CellInfo *getBoundBelCell(BelId bel) const; + + CellInfo *getConflictingBelCell(BelId bel) const; + + BelRange getBels() const; + + Loc getBelLocation(BelId bel) const; + + BelId getBelByLocation(Loc loc) const; + BelRange getBelsByTile(int x, int y) const; + + bool getBelGlobalBuf(BelId bel) const; + + IdString getBelType(BelId bel) const; + + std::vector> getBelAttrs(BelId bel) const; + + WireId getBelPinWire(BelId bel, IdString pin) const; + PortType getBelPinType(BelId bel, IdString pin) const; + std::vector getBelPins(BelId bel) const; + + bool isBelLocked(BelId bel) const; + + // ------------------------------------------------- + + WireId getWireByName(IdString name) const; + + IdString getWireName(WireId wire) const; + + IdString getWireType(WireId wire) const; + std::vector> getWireAttrs(WireId wire) const; + + uint32_t getWireChecksum(WireId wire) const; + + void bindWire(WireId wire, NetInfo *net, PlaceStrength strength); + + void unbindWire(WireId wire); + + bool checkWireAvail(WireId wire) const; + + NetInfo *getBoundWireNet(WireId wire) const; + + WireId getConflictingWireWire(WireId wire) const; + + NetInfo *getConflictingWireNet(WireId wire) const; + + DelayInfo getWireDelay(WireId wire) const; + + BelPinRange getWireBelPins(WireId wire) const; + + WireRange getWires() const; + + // ------------------------------------------------- + + PipId getPipByName(IdString name) const; + + void bindPip(PipId pip, NetInfo *net, PlaceStrength strength); + + void unbindPip(PipId pip); + + bool checkPipAvail(PipId pip) const; + + NetInfo *getBoundPipNet(PipId pip) const; + + WireId getConflictingPipWire(PipId pip) const; + + NetInfo *getConflictingPipNet(PipId pip) const; + + AllPipRange getPips() const; + + Loc getPipLocation(PipId pip) const; + + IdString getPipName(PipId pip) const; + + IdString getPipType(PipId pip) const; + std::vector> getPipAttrs(PipId pip) const; + + uint32_t getPipChecksum(PipId pip) const; + + WireId getPipSrcWire(PipId pip) const; + + WireId getPipDstWire(PipId pip) const; + + DelayInfo getPipDelay(PipId pip) const; + + PipRange getPipsDownhill(WireId wire) const; + + PipRange getPipsUphill(WireId wire) const; + + PipRange getWireAliases(WireId wire) const; + + BelId getPackagePinBel(const std::string &pin) const; + std::string getBelPackagePin(BelId bel) const; + + // ------------------------------------------------- + + GroupId getGroupByName(IdString name) const; + IdString getGroupName(GroupId group) const; + std::vector getGroups() const; + std::vector getGroupBels(GroupId group) const; + std::vector getGroupWires(GroupId group) const; + std::vector getGroupPips(GroupId group) const; + std::vector getGroupGroups(GroupId group) const; + + // ------------------------------------------------- + + delay_t estimateDelay(WireId src, WireId dst) const; + delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const; + delay_t getDelayEpsilon() const; + delay_t getRipupDelayPenalty() const; + float getDelayNS(delay_t v) const; + DelayInfo getDelayFromNS(float ns) const; + uint32_t getDelayChecksum(delay_t v) const; + bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; + + ArcBounds getRouteBoundingBox(WireId src, WireId dst) const; + + // ------------------------------------------------- + + bool pack(); + bool place(); + bool route(); + + // ------------------------------------------------- + + std::vector getDecalGraphics(DecalId decal) const; + + DecalXY getBelDecal(BelId bel) const; + DecalXY getWireDecal(WireId wire) const; + DecalXY getPipDecal(PipId pip) const; + DecalXY getGroupDecal(GroupId group) const; + + // ------------------------------------------------- + + // Get the delay through a cell from one port to another, returning false + // if no path exists. This only considers combinational delays, as required by the Arch API + bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; + // getCellDelayInternal is similar to the above, but without false path checks and including clock to out delays + // for internal arch use only + bool getCellDelayInternal(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; + // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port + TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const; + // Get the TimingClockingInfo of a port + TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const; + // Return true if a port is a net + bool isGlobalNet(const NetInfo *net) const; + + // ------------------------------------------------- + + // Perform placement validity checks, returning false on failure (all + // implemented in arch_place.cc) + + // Whether or not a given cell can be placed at a given Bel + // This is not intended for Bel type checks, but finer-grained constraints + // such as conflicting set/reset signals, etc + bool isValidBelForCell(CellInfo *cell, BelId bel) const; + + // Return true whether all Bels at a given location are valid + bool isBelLocationValid(BelId bel) const; + + // Helper function for above + bool logicCellsCompatible(const CellInfo **it, const size_t size) const; + + // ------------------------------------------------- + // Assign architecure-specific arguments to nets and cells, which must be + // called between packing or further + // netlist modifications, and validity checks + void assignArchInfo(); + void assignCellInfo(CellInfo *cell); + + // ------------------------------------------------- + BelPin getIOBSharingPLLPin(BelId pll, IdString pll_pin) const; + + int getDrivenGlobalNetwork(BelId bel) const; + + static const std::string defaultPlacer; + static const std::vector availablePlacers; + static const std::string defaultRouter; + static const std::vector availableRouters; +}; + +NEXTPNR_NAMESPACE_END diff --git a/cyclonev/archdefs.h b/cyclonev/archdefs.h new file mode 100644 index 00000000..9b72d268 --- /dev/null +++ b/cyclonev/archdefs.h @@ -0,0 +1,187 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 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 NEXTPNR_H +#error Include "archdefs.h" via "nextpnr.h" only. +#endif + +NEXTPNR_NAMESPACE_BEGIN + +typedef int delay_t; + +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 +{ + int32_t index = -1; + + bool operator==(const BelId &other) const { return index == other.index; } + bool operator!=(const BelId &other) const { return index != other.index; } + bool operator<(const BelId &other) const { return index < other.index; } +}; + +struct WireId +{ + int32_t index = -1; + + bool operator==(const WireId &other) const { return index == other.index; } + bool operator!=(const WireId &other) const { return index != other.index; } + bool operator<(const WireId &other) const { return index < other.index; } +}; + +struct PipId +{ + int32_t index = -1; + + bool operator==(const PipId &other) const { return index == other.index; } + bool operator!=(const PipId &other) const { return index != other.index; } + bool operator<(const PipId &other) const { return index < other.index; } +}; + +struct GroupId +{ + enum : int8_t + { + TYPE_NONE + } type = TYPE_NONE; + int8_t x = 0, y = 0; + + bool operator==(const GroupId &other) const { return (type == other.type) && (x == other.x) && (y == other.y); } + bool operator!=(const GroupId &other) const { return (type != other.type) || (x != other.x) || (y == other.y); } +}; + +struct DecalId +{ + enum : int8_t + { + TYPE_NONE, + TYPE_BEL, + TYPE_WIRE, + TYPE_PIP, + TYPE_GROUP + } type = TYPE_NONE; + int32_t index = -1; + bool active = false; + + bool operator==(const DecalId &other) const { return (type == other.type) && (index == other.index); } + bool operator!=(const DecalId &other) const { return (type != other.type) || (index != other.index); } +}; + +struct ArchNetInfo +{ + bool is_global = false; + bool is_reset = false, is_enable = false; +}; + +struct NetInfo; + +struct ArchCellInfo +{ + union + { + struct + { + bool dffEnable; + bool carryEnable; + bool negClk; + int inputCount; + const NetInfo *clk, *cen, *sr; + } lcInfo; + struct + { + bool lvds; + bool global; + bool negtrig; + int pintype; + // TODO: clk packing checks... + } ioInfo; + struct + { + bool forPadIn; + } gbInfo; + struct + { + bool ledCurConnected; + } ledInfo; + }; +}; + +NEXTPNR_NAMESPACE_END + +namespace std { +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept { return hash()(bel.index); } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept + { + return hash()(wire.index); + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept { return hash()(pip.index); } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX GroupId &group) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(group.type)); + boost::hash_combine(seed, hash()(group.x)); + boost::hash_combine(seed, hash()(group.y)); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX DecalId &decal) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(decal.type)); + boost::hash_combine(seed, hash()(decal.index)); + return seed; + } +}; +} // namespace std diff --git a/cyclonev/family.cmake b/cyclonev/family.cmake new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From b8f58d558c3472280cb618343ddd8857c4073d20 Mon Sep 17 00:00:00 2001 From: Dan Ravensloft Date: Wed, 6 Jan 2021 16:22:17 +0000 Subject: couple of functions implemented --- cyclonev/arch.cc | 81 ++++++++++++++++++- cyclonev/arch.h | 218 ++++++---------------------------------------------- cyclonev/archdefs.h | 49 +++--------- 3 files changed, 114 insertions(+), 234 deletions(-) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index e0721791..41c39876 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -1,7 +1,7 @@ /* * nextpnr -- Next Generation Place and Route * - * Copyright (C) 2020 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 @@ -14,5 +14,82 @@ * 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 "nextpnr.h" + +#include "mistral/lib/cyclonev.h" + +NEXTPNR_NAMESPACE_BEGIN + +using mistral::CycloneV; + +Arch::Arch(ArchArgs args) +{ + this->args = args; + this->cyclonev = mistral::CycloneV::get_model(args.device); + NPNR_ASSERT(this->cyclonev != nullptr); +} + +int Arch::getTileBelDimZ(int x, int y) const +{ + 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: + /* + * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB + * is one BEL, but nextpnr wants something with more precision. + * + * One LAB contains 10 ALMs. + * One ALM contains 2 LUT outputs and 4 flop outputs. + */ + return 60; + case CycloneV::block_type_t::GPIO: + // GPIO tiles contain 4 pins. + return 4; + default: + continue; + } + } + + // As a temporary hack, only LABs and IO are allowed to be placed, so every other tile type has zero BELs. + return 0; +} + +BelId Arch::getBelByName(IdString name) const +{ + char bel_type_str[80] = {0}; + int x = 0, y = 0, z = 0; + BelId bel; + + sscanf(name.c_str(this), "%s.%d.%d.%d", bel_type_str, &x, &y, &z); + + auto bel_type = cyclonev->block_type_lookup(std::string{bel_type_str}); + + bel.pos = CycloneV::xy2pos(x, y); + bel.z = (bel_type << 8) | z; + + return bel; +} + +IdString Arch::getBelName(BelId bel) const +{ + char bel_str[80] = {0}; + + int x = CycloneV::pos2x(bel.pos); + int y = CycloneV::pos2y(bel.pos); + int z = bel.pos & 0xFF; + int bel_type = bel.z >> 8; + + snprintf(bel_str, 80, "%s.%03d.%03d.%03d", cyclonev->block_type_names[bel_type], x, y, z); + + return id(bel_str); +} + + + +NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 03b7d54e..5ad13b51 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -1,7 +1,7 @@ /* * nextpnr -- Next Generation Place and Route * - * Copyright (C) 2020 Lofty + * 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 @@ -21,265 +21,95 @@ #error Include "arch.h" via "nextpnr.h" only. #endif -NEXTPNR_NAMESPACE_BEGIN - -struct BelIterator -{ - int cursor; - - BelIterator operator++() - { - cursor++; - return *this; - } - BelIterator operator++(int) - { - BelIterator prior(*this); - cursor++; - return prior; - } - - bool operator!=(const BelIterator &other) const { return cursor != other.cursor; } - - bool operator==(const BelIterator &other) const { return cursor == other.cursor; } - - BelId operator*() const - { - BelId ret; - ret.index = cursor; - return ret; - } -}; - -struct BelRange -{ - BelIterator b, e; - BelIterator begin() const { return b; } - BelIterator end() const { return e; } -}; - -// ----------------------------------------------------------------------- - -struct BelPinIterator -{ - const /* something */ int *ptr = nullptr; - - void operator++() { ptr++; } - bool operator!=(const BelPinIterator &other) const { return ptr != other.ptr; } +#include "mistral/lib/cyclonev.h" - BelPin operator*() const - { - BelPin ret; - return ret; - } -}; - -struct BelPinRange -{ - BelPinIterator b, e; - BelPinIterator begin() const { return b; } - BelPinIterator end() const { return e; } -}; - -// ----------------------------------------------------------------------- - -struct WireIterator -{ - int cursor = -1; - - void operator++() { cursor++; } - bool operator!=(const WireIterator &other) const { return cursor != other.cursor; } - - WireId operator*() const - { - WireId ret; - ret.index = cursor; - return ret; - } -}; - -struct WireRange -{ - WireIterator b, e; - WireIterator begin() const { return b; } - WireIterator end() const { return e; } -}; - -// ----------------------------------------------------------------------- - -struct AllPipIterator -{ - int cursor = -1; - - void operator++() { cursor++; } - bool operator!=(const AllPipIterator &other) const { return cursor != other.cursor; } - - PipId operator*() const - { - PipId ret; - ret.index = cursor; - return ret; - } -}; - -struct AllPipRange -{ - AllPipIterator b, e; - AllPipIterator begin() const { return b; } - AllPipIterator end() const { return e; } -}; - -// ----------------------------------------------------------------------- - -struct PipIterator -{ - const int *cursor = nullptr; - - void operator++() { cursor++; } - bool operator!=(const PipIterator &other) const { return cursor != other.cursor; } - - PipId operator*() const - { - PipId ret; - ret.index = *cursor; - return ret; - } -}; - -struct PipRange -{ - PipIterator b, e; - PipIterator begin() const { return b; } - PipIterator end() const { return e; } -}; +NEXTPNR_NAMESPACE_BEGIN struct ArchArgs { + std::string device; }; struct Arch : BaseCtx { ArchArgs args; + mistral::CycloneV* cyclonev; + Arch(ArchArgs args); - std::string getChipName() const; + std::string getChipName() const { return std::string{"TODO: getChipName"}; } IdString archId() const { return id("cyclonev"); } - ArchArgs archArgs() const; - IdString archArgsToId(ArchArgs args) const; + ArchArgs archArgs() const { return args; } + IdString archArgsToId(ArchArgs args) const { return id("TODO: archArgsToId"); } // ------------------------------------------------- - int getGridDimX() const; - int getGridDimY() const; - int getTileBelDimZ(int, int) const; - int getTilePipDimZ(int, int) const; + int getGridDimX() const { return cyclonev->get_tile_sx(); } + int getGridDimY() const { return cyclonev->get_tile_sy(); } + int getTileBelDimZ(int x, int y) const; // arch.cc + int getTilePipDimZ(int x, int y) const { return 1; } // ------------------------------------------------- - BelId getBelByName(IdString name) const; - - IdString getBelName(BelId bel) const; - - uint32_t getBelChecksum(BelId bel) const; - + BelId getBelByName(IdString name) const; // arch.cc + IdString getBelName(BelId bel) const; // arch.cc + uint32_t getBelChecksum(BelId bel) const { return (bel.pos << 16) | bel.z; } void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength); - void unbindBel(BelId bel); - bool checkBelAvail(BelId bel) const; - CellInfo *getBoundBelCell(BelId bel) const; - CellInfo *getConflictingBelCell(BelId bel) const; - - BelRange getBels() const; - + const std::vector &getBels() const; Loc getBelLocation(BelId bel) const; - BelId getBelByLocation(Loc loc) const; - BelRange getBelsByTile(int x, int y) const; - + const std::vector &getBelsByTile(int x, int y) const; bool getBelGlobalBuf(BelId bel) const; - IdString getBelType(BelId bel) const; - std::vector> getBelAttrs(BelId bel) const; - WireId getBelPinWire(BelId bel, IdString pin) const; PortType getBelPinType(BelId bel, IdString pin) const; std::vector getBelPins(BelId bel) const; - bool isBelLocked(BelId bel) const; // ------------------------------------------------- WireId getWireByName(IdString name) const; - IdString getWireName(WireId wire) const; - IdString getWireType(WireId wire) const; std::vector> getWireAttrs(WireId wire) const; - uint32_t getWireChecksum(WireId wire) const; - void bindWire(WireId wire, NetInfo *net, PlaceStrength strength); - void unbindWire(WireId wire); - bool checkWireAvail(WireId wire) const; - NetInfo *getBoundWireNet(WireId wire) const; - WireId getConflictingWireWire(WireId wire) const; - NetInfo *getConflictingWireNet(WireId wire) const; - DelayInfo getWireDelay(WireId wire) const; - - BelPinRange getWireBelPins(WireId wire) const; - - WireRange getWires() const; + const std::vector &getWireBelPins(WireId wire) const; + const std::vector &getWires() const; // ------------------------------------------------- PipId getPipByName(IdString name) const; - void bindPip(PipId pip, NetInfo *net, PlaceStrength strength); - void unbindPip(PipId pip); - bool checkPipAvail(PipId pip) const; - NetInfo *getBoundPipNet(PipId pip) const; - WireId getConflictingPipWire(PipId pip) const; - NetInfo *getConflictingPipNet(PipId pip) const; - - AllPipRange getPips() const; - + const std::vector &getPips() const; Loc getPipLocation(PipId pip) const; - IdString getPipName(PipId pip) const; - IdString getPipType(PipId pip) const; std::vector> getPipAttrs(PipId pip) const; - uint32_t getPipChecksum(PipId pip) const; - WireId getPipSrcWire(PipId pip) const; - WireId getPipDstWire(PipId pip) const; - DelayInfo getPipDelay(PipId pip) const; - - PipRange getPipsDownhill(WireId wire) const; - - PipRange getPipsUphill(WireId wire) const; - - PipRange getWireAliases(WireId wire) const; - + const std::vector &getPipsDownhill(WireId wire) const; + const std::vector &getPipsUphill(WireId wire) const; + const std::vector &getWireAliases(WireId wire) const; BelId getPackagePinBel(const std::string &pin) const; std::string getBelPackagePin(BelId bel) const; diff --git a/cyclonev/archdefs.h b/cyclonev/archdefs.h index 9b72d268..a108bd6a 100644 --- a/cyclonev/archdefs.h +++ b/cyclonev/archdefs.h @@ -1,7 +1,7 @@ /* * nextpnr -- Next Generation Place and Route * - * Copyright (C) 2020 Lofty + * Copyright (C) 2021 Lofty Date: Wed, 6 Jan 2021 18:07:59 +0000 Subject: Resolve feedback --- cyclonev/arch.cc | 6 +++--- cyclonev/arch.h | 6 +++--- cyclonev/archdefs.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index 41c39876..f5dd35eb 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -24,7 +24,7 @@ NEXTPNR_NAMESPACE_BEGIN -using mistral::CycloneV; +using namespace mistral; Arch::Arch(ArchArgs args) { @@ -66,7 +66,7 @@ BelId Arch::getBelByName(IdString name) const int x = 0, y = 0, z = 0; BelId bel; - sscanf(name.c_str(this), "%s.%d.%d.%d", bel_type_str, &x, &y, &z); + sscanf(name.c_str(this), "%25s.%d.%d.%d", bel_type_str, &x, &y, &z); auto bel_type = cyclonev->block_type_lookup(std::string{bel_type_str}); @@ -82,7 +82,7 @@ IdString Arch::getBelName(BelId bel) const int x = CycloneV::pos2x(bel.pos); int y = CycloneV::pos2y(bel.pos); - int z = bel.pos & 0xFF; + int z = bel.z & 0xFF; int bel_type = bel.z >> 8; snprintf(bel_str, 80, "%s.%03d.%03d.%03d", cyclonev->block_type_names[bel_type], x, y, z); diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 5ad13b51..cdf41f39 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -107,9 +107,9 @@ struct Arch : BaseCtx WireId getPipSrcWire(PipId pip) const; WireId getPipDstWire(PipId pip) const; DelayInfo getPipDelay(PipId pip) const; - const std::vector &getPipsDownhill(WireId wire) const; - const std::vector &getPipsUphill(WireId wire) const; - const std::vector &getWireAliases(WireId wire) const; + const std::vector &getPipsDownhill(WireId wire) const; + const std::vector &getPipsUphill(WireId wire) const; + const std::vector &getWireAliases(WireId wire) const; BelId getPackagePinBel(const std::string &pin) const; std::string getBelPackagePin(BelId bel) const; diff --git a/cyclonev/archdefs.h b/cyclonev/archdefs.h index a108bd6a..c522a83f 100644 --- a/cyclonev/archdefs.h +++ b/cyclonev/archdefs.h @@ -119,7 +119,7 @@ NEXTPNR_NAMESPACE_END namespace std { template <> struct hash { - std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept { return hash()(bel.index); } + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept { return hash()((static_cast(bel.pos) << 16) | bel.z); } }; template <> struct hash -- cgit v1.2.3 From fcdf1e0bfdb45f1ee8a315d1d1ca0a07ca66f7a4 Mon Sep 17 00:00:00 2001 From: Dan Ravensloft Date: Wed, 3 Feb 2021 00:12:14 +0000 Subject: bind/unbind bel --- cyclonev/arch.cc | 16 ++++++++++++++++ cyclonev/arch.h | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index f5dd35eb..ac19b7d2 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -90,6 +90,22 @@ IdString Arch::getBelName(BelId bel) const return id(bel_str); } +void Arch::bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) +{ + bels.at(bel).bound_cell = cell; + cell->bel = bel; + cell->belStrength = strength; + refreshUiBel(bel); +} + +void Arch::unbindBel(BelId bel) +{ + bels.at(bel).bound_cell->bel = BelId(); + bels.at(bel).bound_cell->belStrength = STRENGTH_NONE; + bels.at(bel).bound_cell = nullptr; + refreshUiBel(bel); +} +bool Arch::checkBelAvail(BelId bel) const { return bels.at(bel).bound_cell == nullptr; } NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/cyclonev/arch.h b/cyclonev/arch.h index cdf41f39..cd5905cd 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -30,11 +30,31 @@ struct ArchArgs std::string device; }; +struct BelInfo +{ + IdString name, type; + std::map attrs; + CellInfo *bound_cell; + std::unordered_map pins; + DecalXY decalxy; + int x, y, z; + bool gb; +}; + +struct PinInfo +{ + IdString name; + WireId wire; + PortType type; +}; + struct Arch : BaseCtx { ArchArgs args; mistral::CycloneV* cyclonev; + std::unordered_map bels; + Arch(ArchArgs args); std::string getChipName() const { return std::string{"TODO: getChipName"}; } -- cgit v1.2.3 From 170d6cffddeec70051b8488933c5adec460e6a81 Mon Sep 17 00:00:00 2001 From: Dan Ravensloft Date: Thu, 4 Feb 2021 02:29:59 +0000 Subject: current progress --- cyclonev/arch.cc | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++- cyclonev/arch.h | 40 +++++++++++----------- cyclonev/archdefs.h | 5 +-- 3 files changed, 118 insertions(+), 23 deletions(-) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index ac19b7d2..c4b21b4a 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -106,6 +106,100 @@ void Arch::unbindBel(BelId bel) refreshUiBel(bel); } -bool Arch::checkBelAvail(BelId bel) const { return bels.at(bel).bound_cell == nullptr; } +std::vector Arch::getBels() const +{ + // This should probably be redesigned, but it's a hack. + std::vector bels{}; + + 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: + /* + * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB + * is one BEL, but nextpnr wants something with more precision. + * + * One LAB contains 10 ALMs. + * One ALM contains 2 LUT outputs and 4 flop outputs. + */ + for (int z = 0; z < 60; z++) { + bels.push_back(BelId(pos, z)); + } + case CycloneV::block_type_t::GPIO: + // GPIO tiles contain 4 pins. + for (int z = 0; z < 4; z++) { + bels.push_back(BelId(pos, z)); + } + default: + continue; + } + } + } + } + + return bels; +} + +std::vector Arch::getBelsByTile(int x, int y) const +{ + // This should probably be redesigned, but it's a hack. + std::vector bels{}; + + 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: + /* + * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB + * is one BEL, but nextpnr wants something with more precision. + * + * One LAB contains 10 ALMs. + * One ALM contains 2 LUT outputs and 4 flop outputs. + */ + for (int z = 0; z < 60; z++) { + bels.push_back(BelId(pos, z)); + } + case CycloneV::block_type_t::GPIO: + // GPIO tiles contain 4 pins. + for (int z = 0; z < 4; z++) { + bels.push_back(BelId(pos, z)); + } + default: + continue; + } + } + + return bels; +} + +IdString Arch::getBelType(BelId bel) const +{ + 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: + /* + * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB + * is one BEL, but nextpnr wants something with more precision. + * + * One LAB contains 10 ALMs. + * One ALM contains 2 LUT outputs and 4 flop outputs. + */ + return IdString(this, "LAB"); + case CycloneV::block_type_t::GPIO: + // GPIO tiles contain 4 pins. + return IdString(this, "GPIO"); + default: + continue; + } + } + + return IdString(); +} NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/cyclonev/arch.h b/cyclonev/arch.h index cd5905cd..874e4f3e 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -30,6 +30,13 @@ struct ArchArgs std::string device; }; +struct PinInfo +{ + IdString name; + WireId wire; + PortType type; +}; + struct BelInfo { IdString name, type; @@ -41,19 +48,12 @@ struct BelInfo bool gb; }; -struct PinInfo -{ - IdString name; - WireId wire; - PortType type; -}; - struct Arch : BaseCtx { ArchArgs args; mistral::CycloneV* cyclonev; - std::unordered_map bels; + std::unordered_map bels; Arch(ArchArgs args); @@ -75,18 +75,18 @@ struct Arch : BaseCtx BelId getBelByName(IdString name) const; // arch.cc IdString getBelName(BelId bel) const; // arch.cc uint32_t getBelChecksum(BelId bel) const { return (bel.pos << 16) | bel.z; } - void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength); - void unbindBel(BelId bel); - bool checkBelAvail(BelId bel) const; - CellInfo *getBoundBelCell(BelId bel) const; - CellInfo *getConflictingBelCell(BelId bel) const; - const std::vector &getBels() const; - Loc getBelLocation(BelId bel) const; - BelId getBelByLocation(Loc loc) const; - const std::vector &getBelsByTile(int x, int y) const; - bool getBelGlobalBuf(BelId bel) const; - IdString getBelType(BelId bel) const; - std::vector> getBelAttrs(BelId bel) const; + void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength); // arch.cc + void unbindBel(BelId bel); // arch.cc + bool checkBelAvail(BelId bel) const { return bels.at(bel).bound_cell == nullptr; } + CellInfo *getBoundBelCell(BelId bel) const { return bels.at(bel).bound_cell; } + CellInfo *getConflictingBelCell(BelId bel) const { return nullptr; } // HACK + std::vector getBels() const; // arch.cc + Loc getBelLocation(BelId bel) const { return Loc(CycloneV::pos2x(bel.pos), CycloneV::pos2y(bel.pos), bel.z); } + BelId getBelByLocation(Loc loc) const { return BelId(CycloneV::xy2pos(loc.x, loc.y), loc.z); } + std::vector getBelsByTile(int x, int y) const; // arch.cc + bool getBelGlobalBuf(BelId bel) const { return false; } // HACK + IdString getBelType(BelId bel) const; // arch.cc + std::vector> getBelAttrs(BelId bel) const { return std::vector>{}; } // HACK WireId getBelPinWire(BelId bel, IdString pin) const; PortType getBelPinType(BelId bel, IdString pin) const; std::vector getBelPins(BelId bel) const; diff --git a/cyclonev/archdefs.h b/cyclonev/archdefs.h index c522a83f..de04b4c7 100644 --- a/cyclonev/archdefs.h +++ b/cyclonev/archdefs.h @@ -52,6 +52,9 @@ struct DelayInfo 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; @@ -106,8 +109,6 @@ struct DecalId struct ArchNetInfo { - bool is_global = false; - bool is_reset = false, is_enable = false; }; struct ArchCellInfo -- cgit v1.2.3 From 9f2cbe1762387ec38d358fb4c885740de89b5656 Mon Sep 17 00:00:00 2001 From: Dan Ravensloft Date: Thu, 4 Feb 2021 14:28:39 +0000 Subject: build bel list in constructor --- cyclonev/arch.cc | 76 ++++++++++++++++++++++++-------------------------------- cyclonev/arch.h | 3 ++- 2 files changed, 35 insertions(+), 44 deletions(-) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index c4b21b4a..016b69b6 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -31,6 +31,35 @@ Arch::Arch(ArchArgs args) this->args = args; this->cyclonev = mistral::CycloneV::get_model(args.device); NPNR_ASSERT(this->cyclonev != nullptr); + + 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: + /* + * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB + * is one BEL, but nextpnr wants something with more precision. + * + * One LAB contains 10 ALMs. + * One ALM contains 2 LUT outputs and 4 flop outputs. + */ + for (int z = 0; z < 60; z++) { + this->bel_list.push_back(BelId(pos, z)); + } + case CycloneV::block_type_t::GPIO: + // GPIO tiles contain 4 pins. + for (int z = 0; z < 4; z++) { + this->bel_list.push_back(BelId(pos, z)); + } + default: + continue; + } + } + } + } } int Arch::getTileBelDimZ(int x, int y) const @@ -106,43 +135,6 @@ void Arch::unbindBel(BelId bel) refreshUiBel(bel); } -std::vector Arch::getBels() const -{ - // This should probably be redesigned, but it's a hack. - std::vector bels{}; - - 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: - /* - * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB - * is one BEL, but nextpnr wants something with more precision. - * - * One LAB contains 10 ALMs. - * One ALM contains 2 LUT outputs and 4 flop outputs. - */ - for (int z = 0; z < 60; z++) { - bels.push_back(BelId(pos, z)); - } - case CycloneV::block_type_t::GPIO: - // GPIO tiles contain 4 pins. - for (int z = 0; z < 4; z++) { - bels.push_back(BelId(pos, z)); - } - default: - continue; - } - } - } - } - - return bels; -} - std::vector Arch::getBelsByTile(int x, int y) const { // This should probably be redesigned, but it's a hack. @@ -150,8 +142,8 @@ std::vector Arch::getBelsByTile(int x, int y) const CycloneV::pos_t pos = cyclonev->xy2pos(x, y); - for (CycloneV::block_type_t bel : cyclonev->pos_get_bels(pos)) { - switch (bel) { + for (CycloneV::block_type_t cvbel : cyclonev->pos_get_bels(pos)) { + switch (cvbel) { case CycloneV::block_type_t::LAB: /* * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB @@ -178,10 +170,8 @@ std::vector Arch::getBelsByTile(int x, int y) const IdString Arch::getBelType(BelId bel) const { - CycloneV::pos_t pos = cyclonev->xy2pos(x, y); - - for (CycloneV::block_type_t bel : cyclonev->pos_get_bels(pos)) { - switch (bel) { + for (CycloneV::block_type_t cvbel : cyclonev->pos_get_bels(bel.pos)) { + switch (cvbel) { case CycloneV::block_type_t::LAB: /* * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 874e4f3e..01b548af 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -54,6 +54,7 @@ struct Arch : BaseCtx mistral::CycloneV* cyclonev; std::unordered_map bels; + std::vector bel_list; Arch(ArchArgs args); @@ -80,7 +81,7 @@ struct Arch : BaseCtx bool checkBelAvail(BelId bel) const { return bels.at(bel).bound_cell == nullptr; } CellInfo *getBoundBelCell(BelId bel) const { return bels.at(bel).bound_cell; } CellInfo *getConflictingBelCell(BelId bel) const { return nullptr; } // HACK - std::vector getBels() const; // arch.cc + const std::vector& Arch::getBels() const { return bel_list; } Loc getBelLocation(BelId bel) const { return Loc(CycloneV::pos2x(bel.pos), CycloneV::pos2y(bel.pos), bel.z); } BelId getBelByLocation(Loc loc) const { return BelId(CycloneV::xy2pos(loc.x, loc.y), loc.z); } std::vector getBelsByTile(int x, int y) const; // arch.cc -- cgit v1.2.3 From 0533818ceead805b9afc749d3a23e571e4a21543 Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 30 Apr 2021 18:40:24 +0100 Subject: cyclonev: Update in line with nextpnr changes Signed-off-by: gatecat --- CMakeLists.txt | 2 +- cyclonev/arch.cc | 78 ++++++++---------- cyclonev/arch.h | 214 +++++++++++++++++--------------------------------- cyclonev/archdefs.h | 68 ++++------------ cyclonev/family.cmake | 5 ++ 5 files changed, 128 insertions(+), 239 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cc3e100f..6d007ff7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,7 +95,7 @@ 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) +set(FAMILIES generic ice40 ecp5 nexus gowin fpga_interchange machxo2 cyclonev) set(STABLE_FAMILIES generic ice40 ecp5) set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange machxo2 cyclonev) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index 016b69b6..3f728b91 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -20,7 +20,7 @@ #include "nextpnr.h" -#include "mistral/lib/cyclonev.h" +#include "cyclonev.h" NEXTPNR_NAMESPACE_BEGIN @@ -40,20 +40,22 @@ Arch::Arch(ArchArgs args) switch (bel) { case CycloneV::block_type_t::LAB: /* - * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB - * is one BEL, but nextpnr wants something with more precision. - * - * One LAB contains 10 ALMs. - * One ALM contains 2 LUT outputs and 4 flop outputs. - */ + * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB + * is one BEL, but nextpnr wants something with more precision. + * + * One LAB contains 10 ALMs. + * One ALM contains 2 LUT outputs and 4 flop outputs. + */ for (int z = 0; z < 60; z++) { this->bel_list.push_back(BelId(pos, z)); } + break; case CycloneV::block_type_t::GPIO: // GPIO tiles contain 4 pins. for (int z = 0; z < 4; z++) { this->bel_list.push_back(BelId(pos, z)); } + break; default: continue; } @@ -70,12 +72,12 @@ int Arch::getTileBelDimZ(int x, int y) const switch (bel) { case CycloneV::block_type_t::LAB: /* - * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB - * is one BEL, but nextpnr wants something with more precision. - * - * One LAB contains 10 ALMs. - * One ALM contains 2 LUT outputs and 4 flop outputs. - */ + * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB + * is one BEL, but nextpnr wants something with more precision. + * + * One LAB contains 10 ALMs. + * One ALM contains 2 LUT outputs and 4 flop outputs. + */ return 60; case CycloneV::block_type_t::GPIO: // GPIO tiles contain 4 pins. @@ -89,13 +91,13 @@ int Arch::getTileBelDimZ(int x, int y) const return 0; } -BelId Arch::getBelByName(IdString name) const +BelId Arch::getBelByName(IdStringList name) const { char bel_type_str[80] = {0}; int x = 0, y = 0, z = 0; BelId bel; - sscanf(name.c_str(this), "%25s.%d.%d.%d", bel_type_str, &x, &y, &z); + sscanf(name[0].c_str(this), "%25s.%d.%d.%d", bel_type_str, &x, &y, &z); auto bel_type = cyclonev->block_type_lookup(std::string{bel_type_str}); @@ -105,7 +107,7 @@ BelId Arch::getBelByName(IdString name) const return bel; } -IdString Arch::getBelName(BelId bel) const +IdStringList Arch::getBelName(BelId bel) const { char bel_str[80] = {0}; @@ -116,23 +118,7 @@ IdString Arch::getBelName(BelId bel) const snprintf(bel_str, 80, "%s.%03d.%03d.%03d", cyclonev->block_type_names[bel_type], x, y, z); - return id(bel_str); -} - -void Arch::bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) -{ - bels.at(bel).bound_cell = cell; - cell->bel = bel; - cell->belStrength = strength; - refreshUiBel(bel); -} - -void Arch::unbindBel(BelId bel) -{ - bels.at(bel).bound_cell->bel = BelId(); - bels.at(bel).bound_cell->belStrength = STRENGTH_NONE; - bels.at(bel).bound_cell = nullptr; - refreshUiBel(bel); + return IdStringList(id(bel_str)); } std::vector Arch::getBelsByTile(int x, int y) const @@ -146,20 +132,22 @@ std::vector Arch::getBelsByTile(int x, int y) const switch (cvbel) { case CycloneV::block_type_t::LAB: /* - * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB - * is one BEL, but nextpnr wants something with more precision. - * - * One LAB contains 10 ALMs. - * One ALM contains 2 LUT outputs and 4 flop outputs. - */ + * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB + * is one BEL, but nextpnr wants something with more precision. + * + * One LAB contains 10 ALMs. + * One ALM contains 2 LUT outputs and 4 flop outputs. + */ for (int z = 0; z < 60; z++) { bels.push_back(BelId(pos, z)); } + break; case CycloneV::block_type_t::GPIO: // GPIO tiles contain 4 pins. for (int z = 0; z < 4; z++) { bels.push_back(BelId(pos, z)); } + break; default: continue; } @@ -174,12 +162,12 @@ IdString Arch::getBelType(BelId bel) const switch (cvbel) { case CycloneV::block_type_t::LAB: /* - * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB - * is one BEL, but nextpnr wants something with more precision. - * - * One LAB contains 10 ALMs. - * One ALM contains 2 LUT outputs and 4 flop outputs. - */ + * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB + * is one BEL, but nextpnr wants something with more precision. + * + * One LAB contains 10 ALMs. + * One ALM contains 2 LUT outputs and 4 flop outputs. + */ return IdString(this, "LAB"); case CycloneV::block_type_t::GPIO: // GPIO tiles contain 4 pins. diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 01b548af..84ab72f2 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -17,11 +17,17 @@ * */ -#ifndef NEXTPNR_H -#error Include "arch.h" via "nextpnr.h" only. -#endif +#ifndef MISTRAL_ARCH_H +#define MISTRAL_ARCH_H -#include "mistral/lib/cyclonev.h" +#include +#include + +#include "base_arch.h" +#include "nextpnr_types.h" +#include "relptr.h" + +#include "cyclonev.h" NEXTPNR_NAMESPACE_BEGIN @@ -48,173 +54,95 @@ struct BelInfo bool gb; }; -struct Arch : BaseCtx +struct ArchRanges : BaseArchRanges +{ + using ArchArgsT = ArchArgs; + // Bels + using AllBelsRangeT = const std::vector &; + using TileBelsRangeT = std::vector; + using BelPinsRangeT = std::vector; + // Wires + using AllWiresRangeT = const std::vector &; + using DownhillPipRangeT = const std::vector &; + using UphillPipRangeT = const std::vector &; + using WireBelPinRangeT = std::vector; + // Pips + using AllPipsRangeT = const std::vector &; +}; + +struct Arch : BaseArch { ArchArgs args; - mistral::CycloneV* cyclonev; + mistral::CycloneV *cyclonev; std::unordered_map bels; std::vector bel_list; Arch(ArchArgs args); - std::string getChipName() const { return std::string{"TODO: getChipName"}; } - - IdString archId() const { return id("cyclonev"); } - ArchArgs archArgs() const { return args; } - IdString archArgsToId(ArchArgs args) const { return id("TODO: archArgsToId"); } - + std::string getChipName() const override { return std::string{"TODO: getChipName"}; } // ------------------------------------------------- - int getGridDimX() const { return cyclonev->get_tile_sx(); } - int getGridDimY() const { return cyclonev->get_tile_sy(); } - int getTileBelDimZ(int x, int y) const; // arch.cc - int getTilePipDimZ(int x, int y) const { return 1; } + 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 // ------------------------------------------------- - BelId getBelByName(IdString name) const; // arch.cc - IdString getBelName(BelId bel) const; // arch.cc - uint32_t getBelChecksum(BelId bel) const { return (bel.pos << 16) | bel.z; } - void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength); // arch.cc - void unbindBel(BelId bel); // arch.cc - bool checkBelAvail(BelId bel) const { return bels.at(bel).bound_cell == nullptr; } - CellInfo *getBoundBelCell(BelId bel) const { return bels.at(bel).bound_cell; } - CellInfo *getConflictingBelCell(BelId bel) const { return nullptr; } // HACK - const std::vector& Arch::getBels() const { return bel_list; } - Loc getBelLocation(BelId bel) const { return Loc(CycloneV::pos2x(bel.pos), CycloneV::pos2y(bel.pos), bel.z); } - BelId getBelByLocation(Loc loc) const { return BelId(CycloneV::xy2pos(loc.x, loc.y), loc.z); } - std::vector getBelsByTile(int x, int y) const; // arch.cc - bool getBelGlobalBuf(BelId bel) const { return false; } // HACK - IdString getBelType(BelId bel) const; // arch.cc - std::vector> getBelAttrs(BelId bel) const { return std::vector>{}; } // HACK - WireId getBelPinWire(BelId bel, IdString pin) const; - PortType getBelPinType(BelId bel, IdString pin) const; - std::vector getBelPins(BelId bel) const; - bool isBelLocked(BelId bel) const; + BelId getBelByName(IdStringList name) const override; // arch.cc + IdStringList getBelName(BelId bel) const override; // arch.cc + const std::vector &getBels() const override { return bel_list; } + 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 { 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; + PortType getBelPinType(BelId bel, IdString pin) const override; + std::vector getBelPins(BelId bel) const override; // ------------------------------------------------- - WireId getWireByName(IdString name) const; - IdString getWireName(WireId wire) const; - IdString getWireType(WireId wire) const; - std::vector> getWireAttrs(WireId wire) const; - uint32_t getWireChecksum(WireId wire) const; - void bindWire(WireId wire, NetInfo *net, PlaceStrength strength); - void unbindWire(WireId wire); - bool checkWireAvail(WireId wire) const; - NetInfo *getBoundWireNet(WireId wire) const; - WireId getConflictingWireWire(WireId wire) const; - NetInfo *getConflictingWireNet(WireId wire) const; - DelayInfo getWireDelay(WireId wire) const; - const std::vector &getWireBelPins(WireId wire) const; - const std::vector &getWires() const; + WireId getWireByName(IdStringList name) const override; + IdStringList getWireName(WireId wire) const override; + DelayQuad getWireDelay(WireId wire) const; + std::vector getWireBelPins(WireId wire) const override; + const std::vector &getWires() const override; // ------------------------------------------------- - PipId getPipByName(IdString name) const; - void bindPip(PipId pip, NetInfo *net, PlaceStrength strength); - void unbindPip(PipId pip); - bool checkPipAvail(PipId pip) const; - NetInfo *getBoundPipNet(PipId pip) const; - WireId getConflictingPipWire(PipId pip) const; - NetInfo *getConflictingPipNet(PipId pip) const; - const std::vector &getPips() const; - Loc getPipLocation(PipId pip) const; - IdString getPipName(PipId pip) const; - IdString getPipType(PipId pip) const; - std::vector> getPipAttrs(PipId pip) const; - uint32_t getPipChecksum(PipId pip) const; - WireId getPipSrcWire(PipId pip) const; - WireId getPipDstWire(PipId pip) const; - DelayInfo getPipDelay(PipId pip) const; - const std::vector &getPipsDownhill(WireId wire) const; - const std::vector &getPipsUphill(WireId wire) const; - const std::vector &getWireAliases(WireId wire) const; - BelId getPackagePinBel(const std::string &pin) const; - std::string getBelPackagePin(BelId bel) const; + PipId getPipByName(IdStringList name) const override; + const std::vector &getPips() const override; + Loc getPipLocation(PipId pip) const override; + IdStringList getPipName(PipId pip) const override; + WireId getPipSrcWire(PipId pip) const override; + WireId getPipDstWire(PipId pip) const override; + DelayQuad getPipDelay(PipId pip) const override; + const std::vector &getPipsDownhill(WireId wire) const override; + const std::vector &getPipsUphill(WireId wire) const override; // ------------------------------------------------- - GroupId getGroupByName(IdString name) const; - IdString getGroupName(GroupId group) const; - std::vector getGroups() const; - std::vector getGroupBels(GroupId group) const; - std::vector getGroupWires(GroupId group) const; - std::vector getGroupPips(GroupId group) const; - std::vector getGroupGroups(GroupId group) const; + delay_t estimateDelay(WireId src, WireId dst) const override; + delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override; + delay_t getDelayEpsilon() const override; + delay_t getRipupDelayPenalty() const override; + float getDelayNS(delay_t v) const override; + delay_t getDelayFromNS(float ns) const override; + uint32_t getDelayChecksum(delay_t v) const override; - // ------------------------------------------------- - - delay_t estimateDelay(WireId src, WireId dst) const; - delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const; - delay_t getDelayEpsilon() const; - delay_t getRipupDelayPenalty() const; - float getDelayNS(delay_t v) const; - DelayInfo getDelayFromNS(float ns) const; - uint32_t getDelayChecksum(delay_t v) const; - bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; - - ArcBounds getRouteBoundingBox(WireId src, WireId dst) const; + ArcBounds getRouteBoundingBox(WireId src, WireId dst) const override; // ------------------------------------------------- - bool pack(); - bool place(); - bool route(); - - // ------------------------------------------------- - - std::vector getDecalGraphics(DecalId decal) const; - - DecalXY getBelDecal(BelId bel) const; - DecalXY getWireDecal(WireId wire) const; - DecalXY getPipDecal(PipId pip) const; - DecalXY getGroupDecal(GroupId group) const; + bool pack() override; + bool place() override; + bool route() override; // ------------------------------------------------- - // Get the delay through a cell from one port to another, returning false - // if no path exists. This only considers combinational delays, as required by the Arch API - bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; - // getCellDelayInternal is similar to the above, but without false path checks and including clock to out delays - // for internal arch use only - bool getCellDelayInternal(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; - // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port - TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const; - // Get the TimingClockingInfo of a port - TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const; - // Return true if a port is a net - bool isGlobalNet(const NetInfo *net) const; - - // ------------------------------------------------- - - // Perform placement validity checks, returning false on failure (all - // implemented in arch_place.cc) - - // Whether or not a given cell can be placed at a given Bel - // This is not intended for Bel type checks, but finer-grained constraints - // such as conflicting set/reset signals, etc - bool isValidBelForCell(CellInfo *cell, BelId bel) const; - - // Return true whether all Bels at a given location are valid - bool isBelLocationValid(BelId bel) const; - - // Helper function for above - bool logicCellsCompatible(const CellInfo **it, const size_t size) const; - - // ------------------------------------------------- - // Assign architecure-specific arguments to nets and cells, which must be - // called between packing or further - // netlist modifications, and validity checks - void assignArchInfo(); - void assignCellInfo(CellInfo *cell); - - // ------------------------------------------------- - BelPin getIOBSharingPLLPin(BelId pll, IdString pll_pin) const; - - int getDrivenGlobalNetwork(BelId bel) const; - static const std::string defaultPlacer; static const std::vector availablePlacers; static const std::string defaultRouter; @@ -222,3 +150,5 @@ struct Arch : BaseCtx }; NEXTPNR_NAMESPACE_END + +#endif \ No newline at end of file diff --git a/cyclonev/archdefs.h b/cyclonev/archdefs.h index de04b4c7..d8fd3160 100644 --- a/cyclonev/archdefs.h +++ b/cyclonev/archdefs.h @@ -17,11 +17,15 @@ * */ -#ifndef NEXTPNR_H -#error Include "archdefs.h" via "nextpnr.h" only. -#endif +#ifndef MISTRAL_ARCHDEFS_H +#define MISTRAL_ARCHDEFS_H -#include "mistral/lib/cyclonev.h" +#include + +#include "cyclonev.h" + +#include "idstring.h" +#include "nextpnr_namespaces.h" NEXTPNR_NAMESPACE_BEGIN @@ -82,30 +86,9 @@ struct PipId bool operator<(const PipId &other) const { return index < other.index; } }; -struct GroupId -{ - enum : int8_t - { - TYPE_NONE - } type = TYPE_NONE; - int8_t x = 0, y = 0; - - bool operator==(const GroupId &other) const { return (type == other.type) && (x == other.x) && (y == other.y); } - bool operator!=(const GroupId &other) const { return (type != other.type) || (x != other.x) || (y == other.y); } -}; - -struct DecalId -{ - enum : int8_t - { - TYPE_NONE, - } type = TYPE_NONE; - int32_t index = -1; - bool active = false; - - bool operator==(const DecalId &other) const { return (type == other.type) && (index == other.index); } - bool operator!=(const DecalId &other) const { return (type != other.type) || (index != other.index); } -}; +typedef IdString DecalId; +typedef IdString GroupId; +typedef IdString BelBucketId; struct ArchNetInfo { @@ -120,7 +103,10 @@ 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); } + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept + { + return hash()((static_cast(bel.pos) << 16) | bel.z); + } }; template <> struct hash @@ -136,26 +122,6 @@ template <> struct hash std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept { return hash()(pip.index); } }; -template <> struct hash -{ - std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX GroupId &group) const noexcept - { - std::size_t seed = 0; - boost::hash_combine(seed, hash()(group.type)); - boost::hash_combine(seed, hash()(group.x)); - boost::hash_combine(seed, hash()(group.y)); - return seed; - } -}; - -template <> struct hash -{ - std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX DecalId &decal) const noexcept - { - std::size_t seed = 0; - boost::hash_combine(seed, hash()(decal.type)); - boost::hash_combine(seed, hash()(decal.index)); - return seed; - } -}; } // namespace std + +#endif \ No newline at end of file diff --git a/cyclonev/family.cmake b/cyclonev/family.cmake index e69de29b..9d1fb9bf 100644 --- a/cyclonev/family.cmake +++ b/cyclonev/family.cmake @@ -0,0 +1,5 @@ +set(MISTRAL_ROOT "" CACHE STRING "Mistral install path") + +foreach(family_target ${family_targets}) + target_include_directories(${family_target} PRIVATE ${MISTRAL_ROOT}/lib) +endforeach() -- cgit v1.2.3 From 7e57196cf924237a5b6c77025768fcf225b9aa9d Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 30 Apr 2021 20:52:52 +0100 Subject: cyclonev: Add some useful constids Signed-off-by: gatecat --- cyclonev/arch.cc | 9 +++++++++ cyclonev/archdefs.h | 16 ++++++++++++++++ cyclonev/constids.inc | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 cyclonev/constids.inc diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index 3f728b91..683118b2 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -26,6 +26,15 @@ 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; diff --git a/cyclonev/archdefs.h b/cyclonev/archdefs.h index d8fd3160..71948ca1 100644 --- a/cyclonev/archdefs.h +++ b/cyclonev/archdefs.h @@ -33,6 +33,22 @@ 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; diff --git a/cyclonev/constids.inc b/cyclonev/constids.inc new file mode 100644 index 00000000..7efee143 --- /dev/null +++ b/cyclonev/constids.inc @@ -0,0 +1,51 @@ +X(MISTRAL_COMB) +X(MISTRAL_FF) +X(LAB) +X(MLAB) + +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(MISTRAL_ALUT6) +X(MISTRAL_ALUT5) +X(MISTRAL_ALUT4) +X(MISTRAL_ALUT3) +X(MISTRAL_ALUT2) +X(MISTRAL_NOT) +X(MISTRAL_ALUT_ARITH) + +X(D0) +X(D1) +X(CI) +X(CO) +X(SO) -- cgit v1.2.3 From 9901a5fafc8ea05e21eae434e4a56c921eb951e7 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 1 May 2021 13:12:33 +0100 Subject: cyclonev: Add wire and pip types Signed-off-by: gatecat --- cyclonev/arch.h | 20 ++++++++++++++++++-- cyclonev/archdefs.h | 37 +++++++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 84ab72f2..e1294e3f 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -54,6 +54,22 @@ struct BelInfo bool gb; }; +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; +}; + struct ArchRanges : BaseArchRanges { using ArchArgsT = ArchArgs; @@ -117,8 +133,8 @@ struct Arch : BaseArch const std::vector &getPips() const override; Loc getPipLocation(PipId pip) const override; IdStringList getPipName(PipId pip) const override; - WireId getPipSrcWire(PipId pip) const override; - WireId getPipDstWire(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; const std::vector &getPipsDownhill(WireId wire) const override; const std::vector &getPipsUphill(WireId wire) const override; diff --git a/cyclonev/archdefs.h b/cyclonev/archdefs.h index 71948ca1..249fd3d2 100644 --- a/cyclonev/archdefs.h +++ b/cyclonev/archdefs.h @@ -25,6 +25,7 @@ #include "cyclonev.h" #include "idstring.h" +#include "nextpnr_assertions.h" #include "nextpnr_namespaces.h" NEXTPNR_NAMESPACE_BEGIN @@ -84,22 +85,35 @@ struct BelId 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 { - int32_t index = -1; + 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 index == other.index; } - bool operator!=(const WireId &other) const { return index != other.index; } - bool operator<(const WireId &other) const { return index < other.index; } + 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 { - int32_t index = -1; + 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 index == other.index; } - bool operator!=(const PipId &other) const { return index != other.index; } - bool operator<(const PipId &other) const { return index < other.index; } + 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; @@ -129,13 +143,16 @@ template <> struct hash { std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept { - return hash()(wire.index); + return hash()(wire.node); } }; template <> struct hash { - std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept { return hash()(pip.index); } + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept + { + return hash()((uint64_t(pip.dst) << 32) | pip.src); + } }; } // namespace std -- cgit v1.2.3 From c3cb9aa3f6fb4039716132a23e29259496e43611 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 1 May 2021 13:40:45 +0100 Subject: cyclonev: Add enough stubs that it links Signed-off-by: gatecat --- cyclonev/arch.cc | 21 ++++++++++++- cyclonev/arch.h | 60 +++++++++++++++++++---------------- cyclonev/family.cmake | 8 ++++- cyclonev/main.cc | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 28 deletions(-) create mode 100644 cyclonev/main.cc diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index 683118b2..642806e3 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -38,7 +38,7 @@ void IdString::initialize_arch(const BaseCtx *ctx) Arch::Arch(ArchArgs args) { this->args = args; - this->cyclonev = mistral::CycloneV::get_model(args.device); + this->cyclonev = mistral::CycloneV::get_model(args.device, args.mistral_root); NPNR_ASSERT(this->cyclonev != nullptr); for (int x = 0; x < cyclonev->get_tile_sx(); x++) { @@ -189,4 +189,23 @@ IdString Arch::getBelType(BelId bel) const return IdString(); } +bool Arch::pack() { return true; } +bool Arch::place() { return true; } +bool Arch::route() { return true; } + +#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 index e1294e3f..c23c8cd3 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -34,6 +34,7 @@ NEXTPNR_NAMESPACE_BEGIN struct ArchArgs { std::string device; + std::string mistral_root; }; struct PinInfo @@ -78,12 +79,12 @@ struct ArchRanges : BaseArchRanges using TileBelsRangeT = std::vector; using BelPinsRangeT = std::vector; // Wires - using AllWiresRangeT = const std::vector &; + using AllWiresRangeT = const std::unordered_set &; using DownhillPipRangeT = const std::vector &; using UphillPipRangeT = const std::vector &; - using WireBelPinRangeT = std::vector; + using WireBelPinRangeT = const std::vector &; // Pips - using AllPipsRangeT = const std::vector &; + using AllPipsRangeT = const std::unordered_set &; }; struct Arch : BaseArch @@ -95,6 +96,7 @@ struct Arch : BaseArch std::vector bel_list; Arch(ArchArgs args); + ArchArgs archArgs() const { return args; } std::string getChipName() const override { return std::string{"TODO: getChipName"}; } // ------------------------------------------------- @@ -115,41 +117,41 @@ struct Arch : BaseArch } BelId getBelByLocation(Loc loc) const override { 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; - PortType getBelPinType(BelId bel, IdString pin) const override; - std::vector getBelPins(BelId bel) const override; + WireId getBelPinWire(BelId bel, IdString pin) const override { return WireId(); } + PortType getBelPinType(BelId bel, IdString pin) const override { return PORT_IN; } + std::vector getBelPins(BelId bel) const override { return {}; } // ------------------------------------------------- - WireId getWireByName(IdStringList name) const override; - IdStringList getWireName(WireId wire) const override; - DelayQuad getWireDelay(WireId wire) const; - std::vector getWireBelPins(WireId wire) const override; - const std::vector &getWires() const override; + WireId getWireByName(IdStringList name) const override { return WireId(); } + IdStringList getWireName(WireId wire) const override { return IdStringList(); } + DelayQuad getWireDelay(WireId wire) const override { return DelayQuad(0); } + const std::vector &getWireBelPins(WireId wire) const override { return empty_belpin_list; } + const std::unordered_set &getWires() const override { return all_wires; } // ------------------------------------------------- - PipId getPipByName(IdStringList name) const override; - const std::vector &getPips() const override; - Loc getPipLocation(PipId pip) const override; - IdStringList getPipName(PipId pip) const override; + PipId getPipByName(IdStringList name) const override { return PipId(); } + const std::unordered_set &getPips() const override { return all_pips; } + Loc getPipLocation(PipId pip) const override { return Loc(0, 0, 0); } + IdStringList getPipName(PipId pip) const override { return IdStringList(); } 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; - const std::vector &getPipsDownhill(WireId wire) const override; - const std::vector &getPipsUphill(WireId wire) const override; + DelayQuad getPipDelay(PipId pip) const override { return DelayQuad(0); } + const std::vector &getPipsDownhill(WireId wire) const override { return empty_pip_list; } + const std::vector &getPipsUphill(WireId wire) const override { return empty_pip_list; } // ------------------------------------------------- - delay_t estimateDelay(WireId src, WireId dst) const override; - delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override; - delay_t getDelayEpsilon() const override; - delay_t getRipupDelayPenalty() const override; - float getDelayNS(delay_t v) const override; - delay_t getDelayFromNS(float ns) const override; - uint32_t getDelayChecksum(delay_t v) const override; + 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; + ArcBounds getRouteBoundingBox(WireId src, WireId dst) const override { return ArcBounds(); } // ------------------------------------------------- @@ -163,6 +165,12 @@ struct Arch : BaseArch static const std::vector availablePlacers; static const std::string defaultRouter; static const std::vector availableRouters; + + // WIP to link without failure + std::unordered_set all_wires; + std::unordered_set all_pips; + std::vector empty_pip_list; + std::vector empty_belpin_list; }; NEXTPNR_NAMESPACE_END diff --git a/cyclonev/family.cmake b/cyclonev/family.cmake index 9d1fb9bf..92ec7d12 100644 --- a/cyclonev/family.cmake +++ b/cyclonev/family.cmake @@ -1,5 +1,11 @@ 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) + 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/main.cc b/cyclonev/main.cc new file mode 100644 index 00000000..702b4b7e --- /dev/null +++ b/cyclonev/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 From af0bffbae905d92942492e0fdf42a4b02686b224 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 1 May 2021 14:06:13 +0100 Subject: cyclonev: Add some range types Signed-off-by: gatecat --- cyclonev/arch.h | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/cyclonev/arch.h b/cyclonev/arch.h index c23c8cd3..4e6443c4 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -71,6 +71,61 @@ struct WireInfo 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 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; @@ -79,9 +134,9 @@ struct ArchRanges : BaseArchRanges using TileBelsRangeT = std::vector; using BelPinsRangeT = std::vector; // Wires - using AllWiresRangeT = const std::unordered_set &; - using DownhillPipRangeT = const std::vector &; - using UphillPipRangeT = const std::vector &; + using AllWiresRangeT = AllWireRange; + using DownhillPipRangeT = UpDownhillPipRange; + using UphillPipRangeT = UpDownhillPipRange; using WireBelPinRangeT = const std::vector &; // Pips using AllPipsRangeT = const std::unordered_set &; @@ -127,7 +182,7 @@ struct Arch : BaseArch IdStringList getWireName(WireId wire) const override { return IdStringList(); } DelayQuad getWireDelay(WireId wire) const override { return DelayQuad(0); } const std::vector &getWireBelPins(WireId wire) const override { return empty_belpin_list; } - const std::unordered_set &getWires() const override { return all_wires; } + AllWireRange getWires() const override { return AllWireRange(wires); } // ------------------------------------------------- @@ -138,8 +193,14 @@ struct Arch : BaseArch 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); } - const std::vector &getPipsDownhill(WireId wire) const override { return empty_pip_list; } - const std::vector &getPipsUphill(WireId wire) const override { return empty_pip_list; } + 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); + } // ------------------------------------------------- @@ -166,8 +227,9 @@ struct Arch : BaseArch static const std::string defaultRouter; static const std::vector availableRouters; + std::unordered_map wires; + // WIP to link without failure - std::unordered_set all_wires; std::unordered_set all_pips; std::vector empty_pip_list; std::vector empty_belpin_list; -- cgit v1.2.3 From 5d1b8bf74469d0d5c5cc15e8c8e042da55da5357 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 1 May 2021 14:55:33 +0100 Subject: cyclonev: Add names and archcheck fixes Signed-off-by: gatecat --- common/idstringlist.cc | 19 ++++++++ common/idstringlist.h | 3 ++ cyclonev/arch.cc | 123 ++++++++++++++++++++++++++++++++++--------------- cyclonev/arch.h | 45 +++++++++++------- cyclonev/constids.inc | 2 + 5 files changed, 138 insertions(+), 54 deletions(-) diff --git a/common/idstringlist.cc b/common/idstringlist.cc index 9900f92a..6f6f8cd0 100644 --- a/common/idstringlist.cc +++ b/common/idstringlist.cc @@ -58,4 +58,23 @@ std::string IdStringList::str(const Context *ctx) const return s; } +IdStringList IdStringList::concat(IdStringList a, IdStringList b) +{ + IdStringList result(a.size() + b.size()); + for (size_t i = 0; i < a.size(); i++) + result.ids[i] = a[i]; + for (size_t i = 0; i < b.size(); i++) + result.ids[a.size() + i] = b[i]; + return result; +} + +IdStringList IdStringList::slice(size_t s, size_t e) const +{ + NPNR_ASSERT(e >= s); + IdStringList result(e - s); + for (size_t i = 0; i < result.size(); i++) + result.ids[i] = ids[s + i]; + return result; +} + NEXTPNR_NAMESPACE_END diff --git a/common/idstringlist.h b/common/idstringlist.h index 6a6a554d..24a46731 100644 --- a/common/idstringlist.h +++ b/common/idstringlist.h @@ -64,6 +64,9 @@ struct IdStringList } return false; } + + static IdStringList concat(IdStringList a, IdStringList b); + IdStringList slice(size_t s, size_t e) const; }; NEXTPNR_NAMESPACE_END diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index 642806e3..eff6660e 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -18,6 +18,7 @@ #include +#include "log.h" #include "nextpnr.h" #include "cyclonev.h" @@ -41,6 +42,19 @@ Arch::Arch(ArchArgs 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); + } + 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); @@ -56,13 +70,13 @@ Arch::Arch(ArchArgs args) * One ALM contains 2 LUT outputs and 4 flop outputs. */ for (int z = 0; z < 60; z++) { - this->bel_list.push_back(BelId(pos, z)); + bels[BelId(pos, (bel << 8 | z))]; } break; case CycloneV::block_type_t::GPIO: // GPIO tiles contain 4 pins. for (int z = 0; z < 4; z++) { - this->bel_list.push_back(BelId(pos, z)); + bels[BelId(pos, (bel << 8 | z))]; } break; default: @@ -71,44 +85,25 @@ Arch::Arch(ArchArgs args) } } } + + BaseArch::init_cell_types(); + BaseArch::init_bel_buckets(); } int Arch::getTileBelDimZ(int x, int y) const { - 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: - /* - * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB - * is one BEL, but nextpnr wants something with more precision. - * - * One LAB contains 10 ALMs. - * One ALM contains 2 LUT outputs and 4 flop outputs. - */ - return 60; - case CycloneV::block_type_t::GPIO: - // GPIO tiles contain 4 pins. - return 4; - default: - continue; - } - } - - // As a temporary hack, only LABs and IO are allowed to be placed, so every other tile type has zero BELs. - return 0; + // FIXME: currently encoding type in z (this will be fixed soon when site contents are implemented) + return 16384; } BelId Arch::getBelByName(IdStringList name) const { - char bel_type_str[80] = {0}; - int x = 0, y = 0, z = 0; BelId bel; - - sscanf(name[0].c_str(this), "%25s.%d.%d.%d", bel_type_str, &x, &y, &z); - - auto bel_type = cyclonev->block_type_lookup(std::string{bel_type_str}); + NPNR_ASSERT(name.size() == 4); + auto bel_type = cyclonev->block_type_lookup(name[0].str(this)); + 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 = (bel_type << 8) | z; @@ -118,16 +113,70 @@ BelId Arch::getBelByName(IdStringList name) const IdStringList Arch::getBelName(BelId bel) const { - char bel_str[80] = {0}; - int x = CycloneV::pos2x(bel.pos); int y = CycloneV::pos2y(bel.pos); int z = bel.z & 0xFF; int bel_type = bel.z >> 8; - snprintf(bel_str, 80, "%s.%03d.%03d.%03d", cyclonev->block_type_names[bel_type], x, y, z); + std::array ids{ + id(cyclonev->block_type_names[bel_type]), + int2id.at(x), + int2id.at(y), + int2id.at(z), + }; + + return IdStringList(ids); +} - return IdStringList(id(bel_str)); +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 @@ -148,13 +197,13 @@ std::vector Arch::getBelsByTile(int x, int y) const * One ALM contains 2 LUT outputs and 4 flop outputs. */ for (int z = 0; z < 60; z++) { - bels.push_back(BelId(pos, z)); + bels.push_back(BelId(pos, (cvbel << 8 | z))); } break; case CycloneV::block_type_t::GPIO: // GPIO tiles contain 4 pins. for (int z = 0; z < 4; z++) { - bels.push_back(BelId(pos, z)); + bels.push_back(BelId(pos, (cvbel << 8 | z))); } break; default: diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 4e6443c4..11f0162b 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -46,13 +46,7 @@ struct PinInfo struct BelInfo { - IdString name, type; - std::map attrs; - CellInfo *bound_cell; - std::unordered_map pins; - DecalXY decalxy; - int x, y, z; - bool gb; + // TODO }; struct WireInfo @@ -125,12 +119,13 @@ template struct key_range }; using AllWireRange = key_range>; +using AllBelRange = key_range>; struct ArchRanges : BaseArchRanges { using ArchArgsT = ArchArgs; // Bels - using AllBelsRangeT = const std::vector &; + using AllBelsRangeT = AllBelRange; using TileBelsRangeT = std::vector; using BelPinsRangeT = std::vector; // Wires @@ -147,9 +142,6 @@ struct Arch : BaseArch ArchArgs args; mistral::CycloneV *cyclonev; - std::unordered_map bels; - std::vector bel_list; - Arch(ArchArgs args); ArchArgs archArgs() const { return args; } @@ -159,18 +151,26 @@ struct Arch : BaseArch 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 bel_list; } + AllBelRange getBels() const override { return AllBelRange(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 { return BelId(CycloneV::xy2pos(loc.x, loc.y), loc.z); } + BelId getBelByLocation(Loc loc) const override + { + BelId id = BelId(CycloneV::xy2pos(loc.x, loc.y), loc.z); + if (bels.count(id)) + return id; + else + return BelId(); + } IdString getBelType(BelId bel) const override; // arch.cc WireId getBelPinWire(BelId bel, IdString pin) const override { return WireId(); } PortType getBelPinType(BelId bel, IdString pin) const override { return PORT_IN; } @@ -178,18 +178,18 @@ struct Arch : BaseArch // ------------------------------------------------- - WireId getWireByName(IdStringList name) const override { return WireId(); } - IdStringList getWireName(WireId wire) const override { return IdStringList(); } + 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 empty_belpin_list; } AllWireRange getWires() const override { return AllWireRange(wires); } // ------------------------------------------------- - PipId getPipByName(IdStringList name) const override { return PipId(); } + PipId getPipByName(IdStringList name) const override; const std::unordered_set &getPips() const override { return all_pips; } Loc getPipLocation(PipId pip) const override { return Loc(0, 0, 0); } - IdStringList getPipName(PipId pip) const override { return IdStringList(); } + 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); } @@ -228,11 +228,22 @@ struct Arch : BaseArch static const std::vector availableRouters; std::unordered_map wires; + std::unordered_map bels; // WIP to link without failure std::unordered_set all_pips; std::vector empty_pip_list; 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; }; NEXTPNR_NAMESPACE_END diff --git a/cyclonev/constids.inc b/cyclonev/constids.inc index 7efee143..bb9fee55 100644 --- a/cyclonev/constids.inc +++ b/cyclonev/constids.inc @@ -49,3 +49,5 @@ X(D1) X(CI) X(CO) X(SO) + +X(WIRE) \ No newline at end of file -- cgit v1.2.3 From 8677d59b927ddf60167c15a7422f9deeac5f817a Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 1 May 2021 15:25:43 +0100 Subject: cyclonev: Add routing graph Signed-off-by: gatecat --- cyclonev/arch.cc | 19 +++++++++++++++++++ cyclonev/arch.h | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index eff6660e..10b7a71b 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -55,6 +55,7 @@ Arch::Arch(ArchArgs args) id2rn_t[rnode_id] = CycloneV::rnode_type_t(t); } + log_info("Initialising bels...\n"); 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); @@ -86,6 +87,24 @@ Arch::Arch(ArchArgs args) } } + // 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(); } diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 11f0162b..ce21a63c 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -49,6 +49,9 @@ struct BelInfo // TODO }; +// 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 @@ -102,6 +105,55 @@ struct UpDownhillPipRange 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 { @@ -134,7 +186,7 @@ struct ArchRanges : BaseArchRanges using UphillPipRangeT = UpDownhillPipRange; using WireBelPinRangeT = const std::vector &; // Pips - using AllPipsRangeT = const std::unordered_set &; + using AllPipsRangeT = AllPipRange; }; struct Arch : BaseArch @@ -187,7 +239,7 @@ struct Arch : BaseArch // ------------------------------------------------- PipId getPipByName(IdStringList name) const override; - const std::unordered_set &getPips() const override { return all_pips; } + 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); }; @@ -231,8 +283,6 @@ struct Arch : BaseArch std::unordered_map bels; // WIP to link without failure - std::unordered_set all_pips; - std::vector empty_pip_list; std::vector empty_belpin_list; // Conversion between numbers and rnode types and IdString, for fast wire name implementation -- cgit v1.2.3 From b1d3eb07c3998e8ce71f770eb56d4fe96b60c785 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 2 May 2021 11:08:01 +0100 Subject: archcheck: Use old connectivity check for CycloneV Signed-off-by: gatecat --- common/archcheck.cc | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/common/archcheck.cc b/common/archcheck.cc index 6702032e..80d8ef58 100644 --- a/common/archcheck.cc +++ b/common/archcheck.cc @@ -28,6 +28,12 @@ USING_NEXTPNR_NAMESPACE +#ifndef ARCH_CYCLONEV +// The LRU cache to reduce memory usage during the connectivity check relies on getPips() having some spacial locality, +// which the current CycloneV arch impl doesn't have. This may be fixed in the future, though. +#define USING_LRU_CACHE +#endif + namespace { void archcheck_names(const Context *ctx) @@ -248,6 +254,11 @@ void archcheck_conn(const Context *ctx) log_info("Checking all wires...\n"); +#ifndef USING_LRU_CACHE + std::unordered_map pips_downhill; + std::unordered_map pips_uphill; +#endif + for (WireId wire : ctx->getWires()) { for (BelPin belpin : ctx->getWireBelPins(wire)) { WireId wire2 = ctx->getBelPinWire(belpin.bel, belpin.pin); @@ -257,11 +268,19 @@ void archcheck_conn(const Context *ctx) for (PipId pip : ctx->getPipsDownhill(wire)) { WireId wire2 = ctx->getPipSrcWire(pip); log_assert(wire == wire2); +#ifndef USING_LRU_CACHE + auto result = pips_downhill.emplace(pip, wire); + log_assert(result.second); +#endif } for (PipId pip : ctx->getPipsUphill(wire)) { WireId wire2 = ctx->getPipDstWire(pip); log_assert(wire == wire2); +#ifndef USING_LRU_CACHE + auto result = pips_uphill.emplace(pip, wire); + log_assert(result.second); +#endif } } @@ -285,7 +304,7 @@ void archcheck_conn(const Context *ctx) log_assert(found_belpin); } } - +#ifdef USING_LRU_CACHE // This cache is used to meet two goals: // - Avoid linear scan by invoking getPipsDownhill/getPipsUphill directly. // - Avoid having pip -> wire maps for the entire part. @@ -295,16 +314,25 @@ void archcheck_conn(const Context *ctx) // pip -> wire, assuming that pips are returned from getPips with some // chip locality. LruWireCacheMap pip_cache(ctx, /*cache_size=*/64 * 1024); +#endif log_info("Checking all PIPs...\n"); for (PipId pip : ctx->getPips()) { WireId src_wire = ctx->getPipSrcWire(pip); if (src_wire != WireId()) { +#ifdef USING_LRU_CACHE log_assert(pip_cache.isPipDownhill(pip, src_wire)); +#else + log_assert(pips_downhill.at(pip) == src_wire); +#endif } WireId dst_wire = ctx->getPipDstWire(pip); if (dst_wire != WireId()) { +#ifdef USING_LRU_CACHE log_assert(pip_cache.isPipUphill(pip, dst_wire)); +#else + log_assert(pips_uphill.at(pip) == dst_wire); +#endif } } } -- cgit v1.2.3 From c671961c188ef57637fcecae4decf55fecc30491 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 2 May 2021 20:40:27 +0100 Subject: cyclonev: Outline functions for creating bels/wires/pips Signed-off-by: gatecat --- cyclonev/arch.cc | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ cyclonev/arch.h | 47 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index 10b7a71b..64444695 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -261,6 +261,66 @@ 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, IdString bucket) +{ + // TODO: nothing else is using this BelId system yet... + // TODO (tomorrow?): we probably want a belsByTile type arrangement, similar for wires and pips, for better spacial + // locality + int z = 0; + BelId id; + // Determine a unique z-coordinate + while (bels.count(id = BelId(CycloneV::xy2pos(x, y), z))) + z++; + auto &bel = bels[id]; + bel.name = name; + bel.type = type; + bel.bucket = bucket; + 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; + 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) +{ + bels[bel].pins[pin].dir = dir; + bels[bel].pins[pin].wire = wire; + + BelPin bel_pin; + bel_pin.bel = bel; + bel_pin.pin = pin; + wires[wire].bel_pins.push_back(bel_pin); +} + #ifdef WITH_HEAP const std::string Arch::defaultPlacer = "heap"; #else diff --git a/cyclonev/arch.h b/cyclonev/arch.h index ce21a63c..1f545134 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -37,16 +37,45 @@ struct ArchArgs 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; + // TODO: LAB configuration (control set etc) +}; + struct PinInfo { - IdString name; WireId wire; - PortType type; + PortType dir; }; struct BelInfo { - // TODO + IdString name; + IdString type; + IdString bucket; + int z; + 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 + } labData; + }; }; // We maintain our own wire data based on mistral's. This gets us the bidirectional linking that nextpnr needs, @@ -272,6 +301,18 @@ struct Arch : BaseArch bool place() override; bool route() override; + // ------------------------------------------------- + // Functions for device setup + + BelId add_bel(int x, int y, IdString name, IdString type, IdString bucket); + 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); + + void create_lab(int x, int y); + void create_gpio(int x, int y); + // ------------------------------------------------- static const std::string defaultPlacer; -- cgit v1.2.3 From 86ce6abf6a47f0f452dfe0045cec762d986e7ada Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 2 May 2021 21:41:09 +0100 Subject: cyclonev: Outline LAB structure Signed-off-by: gatecat --- cyclonev/arch.cc | 4 +- cyclonev/arch.h | 16 ++++- cyclonev/constids.inc | 5 ++ cyclonev/lab.cc | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 cyclonev/lab.cc diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index 64444695..f41eb10e 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -261,7 +261,7 @@ 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, IdString bucket) +BelId Arch::add_bel(int x, int y, IdString name, IdString type) { // TODO: nothing else is using this BelId system yet... // TODO (tomorrow?): we probably want a belsByTile type arrangement, similar for wires and pips, for better spacial @@ -274,7 +274,7 @@ BelId Arch::add_bel(int x, int y, IdString name, IdString type, IdString bucket) auto &bel = bels[id]; bel.name = name; bel.type = type; - bel.bucket = bucket; + bel.bucket = type; return id; } diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 1f545134..0614860d 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -49,6 +49,10 @@ struct ALMInfo 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) }; @@ -74,7 +78,7 @@ struct BelInfo 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 - } labData; + } lab_data; }; }; @@ -304,12 +308,17 @@ struct Arch : BaseArch // ------------------------------------------------- // Functions for device setup - BelId add_bel(int x, int y, IdString name, IdString type, IdString bucket); + 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); void create_gpio(int x, int y); @@ -323,6 +332,9 @@ struct Arch : BaseArch std::unordered_map wires; std::unordered_map bels; + // List of LABs + std::vector labs; + // WIP to link without failure std::vector empty_belpin_list; diff --git a/cyclonev/constids.inc b/cyclonev/constids.inc index bb9fee55..a22bda47 100644 --- a/cyclonev/constids.inc +++ b/cyclonev/constids.inc @@ -36,6 +36,11 @@ 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) diff --git a/cyclonev/lab.cc b/cyclonev/lab.cc new file mode 100644 index 00000000..b464b88a --- /dev/null +++ b/cyclonev/lab.cc @@ -0,0 +1,186 @@ +/* + * 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" + +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 +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_CIN, PORT_OUT, carry_in); + arch->add_bel_pin(bel, id_SHAREIN, 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; + arch->bels.at(bel).lab_data.lab = lab_idx; + arch->bels.at(bel).lab_data.alm = z; + arch->bels.at(bel).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; i++) { + 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; + arch->bels.at(bel).lab_data.lab = lab_idx; + arch->bels.at(bel).lab_data.alm = z; + arch->bels.at(bel).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); + } +} + +NEXTPNR_NAMESPACE_END \ No newline at end of file -- cgit v1.2.3 From 431c4cec9ff9652ddafabac7244f19f425ff3e06 Mon Sep 17 00:00:00 2001 From: gatecat Date: Mon, 3 May 2021 15:12:24 +0100 Subject: cyclonev: Rework bels Signed-off-by: gatecat --- cyclonev/arch.cc | 109 +++++++++++++++---------------------------------------- cyclonev/arch.h | 50 +++++++++++++++++++------ cyclonev/lab.cc | 14 ++++--- 3 files changed, 76 insertions(+), 97 deletions(-) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index f41eb10e..d8464ca2 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -56,6 +56,7 @@ Arch::Arch(ArchArgs args) } 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); @@ -63,22 +64,11 @@ Arch::Arch(ArchArgs args) for (CycloneV::block_type_t bel : cyclonev->pos_get_bels(pos)) { switch (bel) { case CycloneV::block_type_t::LAB: - /* - * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB - * is one BEL, but nextpnr wants something with more precision. - * - * One LAB contains 10 ALMs. - * One ALM contains 2 LUT outputs and 4 flop outputs. - */ - for (int z = 0; z < 60; z++) { - bels[BelId(pos, (bel << 8 | z))]; - } + create_lab(x, y); break; case CycloneV::block_type_t::GPIO: - // GPIO tiles contain 4 pins. - for (int z = 0; z < 4; z++) { - bels[BelId(pos, (bel << 8 | z))]; - } + // GPIO tiles contain 4 pins + // TODO break; default: continue; @@ -111,21 +101,22 @@ Arch::Arch(ArchArgs args) int Arch::getTileBelDimZ(int x, int y) const { - // FIXME: currently encoding type in z (this will be fixed soon when site contents are implemented) - return 16384; + // This seems like a reasonable upper bound + return 256; } BelId Arch::getBelByName(IdStringList name) const { BelId bel; NPNR_ASSERT(name.size() == 4); - auto bel_type = cyclonev->block_type_lookup(name[0].str(this)); 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 = (bel_type << 8) | z; + bel.z = z; + + NPNR_ASSERT(name[0] == getBelType(bel)); return bel; } @@ -135,10 +126,9 @@ IdStringList Arch::getBelName(BelId bel) const int x = CycloneV::pos2x(bel.pos); int y = CycloneV::pos2y(bel.pos); int z = bel.z & 0xFF; - int bel_type = bel.z >> 8; std::array ids{ - id(cyclonev->block_type_names[bel_type]), + getBelType(bel), int2id.at(x), int2id.at(y), int2id.at(z), @@ -201,60 +191,23 @@ IdStringList Arch::getPipName(PipId pip) const std::vector Arch::getBelsByTile(int x, int y) const { // This should probably be redesigned, but it's a hack. - std::vector bels{}; - - CycloneV::pos_t pos = cyclonev->xy2pos(x, y); - - for (CycloneV::block_type_t cvbel : cyclonev->pos_get_bels(pos)) { - switch (cvbel) { - case CycloneV::block_type_t::LAB: - /* - * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB - * is one BEL, but nextpnr wants something with more precision. - * - * One LAB contains 10 ALMs. - * One ALM contains 2 LUT outputs and 4 flop outputs. - */ - for (int z = 0; z < 60; z++) { - bels.push_back(BelId(pos, (cvbel << 8 | z))); - } - break; - case CycloneV::block_type_t::GPIO: - // GPIO tiles contain 4 pins. - for (int z = 0; z < 4; z++) { - bels.push_back(BelId(pos, (cvbel << 8 | z))); - } - break; - default: - continue; - } + 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 -{ - for (CycloneV::block_type_t cvbel : cyclonev->pos_get_bels(bel.pos)) { - switch (cvbel) { - case CycloneV::block_type_t::LAB: - /* - * nextpnr and mistral disagree on what a BEL is: mistral thinks an entire LAB - * is one BEL, but nextpnr wants something with more precision. - * - * One LAB contains 10 ALMs. - * One ALM contains 2 LUT outputs and 4 flop outputs. - */ - return IdString(this, "LAB"); - case CycloneV::block_type_t::GPIO: - // GPIO tiles contain 4 pins. - return IdString(this, "GPIO"); - default: - continue; - } - } +IdString Arch::getBelType(BelId bel) const { return bel_data(bel).type; } - return IdString(); +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::pack() { return true; } @@ -263,17 +216,14 @@ bool Arch::route() { return true; } BelId Arch::add_bel(int x, int y, IdString name, IdString type) { - // TODO: nothing else is using this BelId system yet... - // TODO (tomorrow?): we probably want a belsByTile type arrangement, similar for wires and pips, for better spacial - // locality - int z = 0; - BelId id; - // Determine a unique z-coordinate - while (bels.count(id = BelId(CycloneV::xy2pos(x, y), z))) - z++; - auto &bel = bels[id]; + 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; } @@ -312,8 +262,9 @@ PipId Arch::add_pip(WireId src, WireId dst) void Arch::add_bel_pin(BelId bel, IdString pin, PortType dir, WireId wire) { - bels[bel].pins[pin].dir = dir; - bels[bel].pins[pin].wire = wire; + auto &b = bel_data(bel); + b.pins[pin].dir = dir; + b.pins[pin].wire = wire; BelPin bel_pin; bel_pin.bel = bel; diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 0614860d..5ecba367 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -67,7 +67,9 @@ struct BelInfo IdString name; IdString type; IdString bucket; - int z; + // 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 @@ -204,13 +206,12 @@ template struct key_range }; using AllWireRange = key_range>; -using AllBelRange = key_range>; struct ArchRanges : BaseArchRanges { using ArchArgsT = ArchArgs; // Bels - using AllBelsRangeT = AllBelRange; + using AllBelsRangeT = const std::vector &; using TileBelsRangeT = std::vector; using BelPinsRangeT = std::vector; // Wires @@ -242,7 +243,7 @@ struct Arch : BaseArch BelId getBelByName(IdStringList name) const override; // arch.cc IdStringList getBelName(BelId bel) const override; // arch.cc - AllBelRange getBels() const override { return AllBelRange(bels); } + const std::vector &getBels() const override { return all_bels; } std::vector getBelsByTile(int x, int y) const override; Loc getBelLocation(BelId bel) const override { @@ -250,16 +251,27 @@ struct Arch : BaseArch } BelId getBelByLocation(Loc loc) const override { - BelId id = BelId(CycloneV::xy2pos(loc.x, loc.y), loc.z); - if (bels.count(id)) - return id; - else + 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 { return WireId(); } - PortType getBelPinType(BelId bel, IdString pin) const override { return PORT_IN; } - std::vector getBelPins(BelId bel) const override { return {}; } + 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; // ------------------------------------------------- @@ -330,7 +342,6 @@ struct Arch : BaseArch static const std::vector availableRouters; std::unordered_map wires; - std::unordered_map bels; // List of LABs std::vector labs; @@ -347,6 +358,21 @@ struct Arch : BaseArch // 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); } }; NEXTPNR_NAMESPACE_END diff --git a/cyclonev/lab.cc b/cyclonev/lab.cc index b464b88a..55602da4 100644 --- a/cyclonev/lab.cc +++ b/cyclonev/lab.cc @@ -79,9 +79,10 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) arch->add_bel_pin(bel, id_COMBOUT, PORT_OUT, comb_out); // Assign indexing lab.alms.at(z).lut_bels.at(i) = bel; - arch->bels.at(bel).lab_data.lab = lab_idx; - arch->bels.at(bel).lab_data.alm = z; - arch->bels.at(bel).lab_data.idx = i; + 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; @@ -138,9 +139,10 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) } lab.alms.at(z).ff_bels.at(i) = bel; - arch->bels.at(bel).lab_data.lab = lab_idx; - arch->bels.at(bel).lab_data.alm = z; - arch->bels.at(bel).lab_data.idx = i; + auto &b = arch->bel_data(bel); + b.lab_data.lab = lab_idx; + b.lab_data.alm = z; + b.lab_data.idx = i; } } } // namespace -- cgit v1.2.3 From 24af19b58d4b0b4db7d51d4f76c9334580386e5b Mon Sep 17 00:00:00 2001 From: gatecat Date: Mon, 3 May 2021 15:19:52 +0100 Subject: cyclonev: Fix some archcheck fails Signed-off-by: gatecat --- cyclonev/arch.cc | 2 ++ cyclonev/arch.h | 2 +- cyclonev/lab.cc | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index d8464ca2..98a09d3b 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -249,6 +249,7 @@ WireId Arch::add_wire(int x, int y, IdString name, uint64_t flags) z++; wires[id].name_override = name; wires[id].flags = flags; + npnr_wirebyname[full_name] = id; return id; } } @@ -263,6 +264,7 @@ PipId Arch::add_pip(WireId src, WireId dst) 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; diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 5ecba367..15c647aa 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -278,7 +278,7 @@ struct Arch : BaseArch 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 empty_belpin_list; } + const std::vector &getWireBelPins(WireId wire) const override { return wires.at(wire).bel_pins; } AllWireRange getWires() const override { return AllWireRange(wires); } // ------------------------------------------------- diff --git a/cyclonev/lab.cc b/cyclonev/lab.cc index 55602da4..a4619a8c 100644 --- a/cyclonev/lab.cc +++ b/cyclonev/lab.cc @@ -72,8 +72,8 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) // 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_CIN, PORT_OUT, carry_in); - arch->add_bel_pin(bel, id_SHAREIN, PORT_OUT, share_out); + 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); @@ -93,7 +93,7 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) 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; i++) { + 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) -- cgit v1.2.3 From 9bd7ef5f5fcb77e36a988b0967a59965cfe55192 Mon Sep 17 00:00:00 2001 From: gatecat Date: Mon, 3 May 2021 18:54:31 +0100 Subject: cyclonev: Preparations for validity checking Signed-off-by: gatecat --- cyclonev/arch.h | 6 ++++++ cyclonev/archdefs.h | 31 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 15c647aa..53fa4174 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -336,6 +336,12 @@ struct Arch : BaseArch // ------------------------------------------------- + bool is_comb_cell(IdString cell_type) const; + bool is_alm_legal(uint32_t lab, uint8_t alm) const; + bool is_lab_ctrlset_legal(uint32_t lab) const; + + // ------------------------------------------------- + static const std::string defaultPlacer; static const std::vector availablePlacers; static const std::string defaultRouter; diff --git a/cyclonev/archdefs.h b/cyclonev/archdefs.h index 249fd3d2..d6555887 100644 --- a/cyclonev/archdefs.h +++ b/cyclonev/archdefs.h @@ -124,8 +124,39 @@ struct ArchNetInfo { }; +struct ArchPinInfo +{ + // An inverter (INV) has been pushed onto this signal + bool inverted; + // The physical bel pins that this logical pin maps to + std::vector bel_pins; +}; + +struct NetInfo; + struct ArchCellInfo { + union + { + struct + { + // Store the nets here for fast validity checking (avoids too many map lookups in a hot path) + std::array input_sigs; + const NetInfo *comb_out; + + int lut_input_count; + int lut_bits_count; + + bool is_carry, is_shared, is_extended; + } combInfo; + struct + { + const NetInfo *clk, *ena, *aclr, *sclr, *sload, *sdata, *datain; + bool clk_inv, ena_inv, aclr_inv, sclr_inv, sload_inv; + } ffInfo; + }; + + std::unordered_map pin_data; }; NEXTPNR_NAMESPACE_END -- cgit v1.2.3 From 1cd22b81daa4c87870f65dedef74dba02adac8fe Mon Sep 17 00:00:00 2001 From: gatecat Date: Mon, 3 May 2021 20:37:59 +0100 Subject: cyclonev: More preparations for validity checking Signed-off-by: gatecat --- cyclonev/arch.cc | 17 +++++++++ cyclonev/arch.h | 21 ++++++++--- cyclonev/archdefs.h | 28 +++++++++++++-- cyclonev/constids.inc | 1 + cyclonev/lab.cc | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 159 insertions(+), 7 deletions(-) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index 98a09d3b..62acf9e2 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -210,6 +210,23 @@ std::vector Arch::getBelPins(BelId bel) const 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 + return bel_type == cell_type; +} + +BelBucketId Arch::getBelBucketForCellType(IdString cell_type) const { + if (is_comb_cell(cell_type)) + return id_MISTRAL_COMB; + else + return cell_type; +} + bool Arch::pack() { return true; } bool Arch::place() { return true; } bool Arch::route() { return true; } diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 53fa4174..34d90a04 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -44,6 +44,8 @@ struct ALMInfo std::array lut_bels; std::array ff_bels; // TODO: ALM configuration (L5/L6 mode, LUT input permutation, etc) + // So we only validity-check changed parts + bool valid = false, dirty = false; }; struct LABInfo @@ -54,6 +56,9 @@ struct LABInfo std::array aclr_wires; WireId sclr_wire, sload_wire; // TODO: LAB configuration (control set etc) + + // These apply to the validity-checking status of the shared FF control sets + bool ctrl_valid = false, ctrl_dirty = false; }; struct PinInfo @@ -313,6 +318,11 @@ struct Arch : BaseArch // ------------------------------------------------- + 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; @@ -331,14 +341,17 @@ struct Arch : BaseArch return WireId(cyclonev->pnode_to_rnode(CycloneV::pnode(bt, x, y, port, bi, pi))); } - void create_lab(int x, int y); + void create_lab(int x, int y); // lab.cc void create_gpio(int x, int y); // ------------------------------------------------- - bool is_comb_cell(IdString cell_type) const; - bool is_alm_legal(uint32_t lab, uint8_t alm) const; - bool is_lab_ctrlset_legal(uint32_t lab) const; + 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 // ------------------------------------------------- diff --git a/cyclonev/archdefs.h b/cyclonev/archdefs.h index d6555887..5afdd783 100644 --- a/cyclonev/archdefs.h +++ b/cyclonev/archdefs.h @@ -134,6 +134,28 @@ struct ArchPinInfo 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 { union @@ -141,7 +163,7 @@ struct ArchCellInfo struct { // Store the nets here for fast validity checking (avoids too many map lookups in a hot path) - std::array input_sigs; + std::array lut_in; const NetInfo *comb_out; int lut_input_count; @@ -151,8 +173,8 @@ struct ArchCellInfo } combInfo; struct { - const NetInfo *clk, *ena, *aclr, *sclr, *sload, *sdata, *datain; - bool clk_inv, ena_inv, aclr_inv, sclr_inv, sload_inv; + FFControlSet ctrlset; + const NetInfo *sdata, *datain; } ffInfo; }; diff --git a/cyclonev/constids.inc b/cyclonev/constids.inc index a22bda47..90d5e753 100644 --- a/cyclonev/constids.inc +++ b/cyclonev/constids.inc @@ -47,6 +47,7 @@ X(MISTRAL_ALUT4) X(MISTRAL_ALUT3) X(MISTRAL_ALUT2) X(MISTRAL_NOT) +X(MISTRAL_CONST) X(MISTRAL_ALUT_ARITH) X(D0) diff --git a/cyclonev/lab.cc b/cyclonev/lab.cc index a4619a8c..2c2c619a 100644 --- a/cyclonev/lab.cc +++ b/cyclonev/lab.cc @@ -19,11 +19,14 @@ #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) { @@ -185,4 +188,100 @@ void Arch::create_lab(int x, int y) } } +// 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).inverted; + else + result.inverted = false; + return result; + } +} + +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_NOT: + ++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); + } +} + +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); +} + NEXTPNR_NAMESPACE_END \ No newline at end of file -- cgit v1.2.3 From fbdcfa9c42a2a8ed69b0130fcce56644bce3c64e Mon Sep 17 00:00:00 2001 From: gatecat Date: Tue, 4 May 2021 21:23:11 +0100 Subject: cyclonev: First (untested) pass at ALM validity checking Signed-off-by: gatecat --- cyclonev/arch.cc | 6 +- cyclonev/lab.cc | 169 ++++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 135 insertions(+), 40 deletions(-) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index 62acf9e2..8dd21499 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -210,7 +210,8 @@ std::vector Arch::getBelPins(BelId bel) const return pins; } -bool Arch::isValidBelForCellType(IdString cell_type, BelId bel) const { +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); @@ -220,7 +221,8 @@ bool Arch::isValidBelForCellType(IdString cell_type, BelId bel) const { return bel_type == cell_type; } -BelBucketId Arch::getBelBucketForCellType(IdString cell_type) const { +BelBucketId Arch::getBelBucketForCellType(IdString cell_type) const +{ if (is_comb_cell(cell_type)) return id_MISTRAL_COMB; else diff --git a/cyclonev/lab.cc b/cyclonev/lab.cc index 2c2c619a..4e10fe59 100644 --- a/cyclonev/lab.cc +++ b/cyclonev/lab.cc @@ -190,16 +190,17 @@ void Arch::create_lab(int x, int y) // 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).inverted; - else - result.inverted = false; - return result; - } +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).inverted; + else + result.inverted = false; + return result; } +} // namespace bool Arch::is_comb_cell(IdString cell_type) const { @@ -238,35 +239,35 @@ void Arch::assign_comb_info(CellInfo *cell) const } 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_NOT: - ++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)); + 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_NOT: + ++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); @@ -284,4 +285,96 @@ void Arch::assign_ff_info(CellInfo *cell) const 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 the LUT is using less than 6 inputs - TODO: is this correct considering all possible LUT + // sharing + bool ef_available = (!luts[i] || luts[i]->combInfo.lut_input_count < 6); + // 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; +} + NEXTPNR_NAMESPACE_END \ No newline at end of file -- cgit v1.2.3 From a6ea72fd84706de8bdb6fd28005e3c735903ad4c Mon Sep 17 00:00:00 2001 From: gatecat Date: Thu, 6 May 2021 13:49:26 +0100 Subject: cyclonev: Add validity check and IO bels Signed-off-by: gatecat --- cyclonev/arch.cc | 19 ++++++++++++++++-- cyclonev/arch.h | 15 ++++++++------- cyclonev/constids.inc | 8 ++++++++ cyclonev/io.cc | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ cyclonev/lab.cc | 6 ++++++ 5 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 cyclonev/io.cc diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index 8dd21499..9aee5a59 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -67,8 +67,7 @@ Arch::Arch(ArchArgs args) create_lab(x, y); break; case CycloneV::block_type_t::GPIO: - // GPIO tiles contain 4 pins - // TODO + create_gpio(x, y); break; default: continue; @@ -137,6 +136,18 @@ IdStringList Arch::getBelName(BelId bel) const 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 @@ -217,6 +228,8 @@ bool Arch::isValidBelForCellType(IdString cell_type, BelId bel) const 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; } @@ -225,6 +238,8 @@ 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; } diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 34d90a04..301f19d8 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -44,8 +44,6 @@ struct ALMInfo std::array lut_bels; std::array ff_bels; // TODO: ALM configuration (L5/L6 mode, LUT input permutation, etc) - // So we only validity-check changed parts - bool valid = false, dirty = false; }; struct LABInfo @@ -56,9 +54,6 @@ struct LABInfo std::array aclr_wires; WireId sclr_wire, sload_wire; // TODO: LAB configuration (control set etc) - - // These apply to the validity-checking status of the shared FF control sets - bool ctrl_valid = false, ctrl_dirty = false; }; struct PinInfo @@ -278,6 +273,8 @@ struct Arch : BaseArch 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; @@ -341,8 +338,8 @@ struct Arch : BaseArch 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); + void create_lab(int x, int y); // lab.cc + void create_gpio(int x, int y); // io.cc // ------------------------------------------------- @@ -355,6 +352,10 @@ struct Arch : BaseArch // ------------------------------------------------- + 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; diff --git a/cyclonev/constids.inc b/cyclonev/constids.inc index 90d5e753..fcc723f8 100644 --- a/cyclonev/constids.inc +++ b/cyclonev/constids.inc @@ -3,6 +3,14 @@ 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) diff --git a/cyclonev/io.cc b/cyclonev/io.cc new file mode 100644 index 00000000..f2517e5d --- /dev/null +++ b/cyclonev/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/cyclonev/lab.cc b/cyclonev/lab.cc index 4e10fe59..6f7c5e6d 100644 --- a/cyclonev/lab.cc +++ b/cyclonev/lab.cc @@ -377,4 +377,10 @@ bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const return true; } +bool Arch::is_lab_ctrlset_legal(uint32_t lab) const +{ + // TODO + return true; +} + NEXTPNR_NAMESPACE_END \ No newline at end of file -- cgit v1.2.3 From 9eb0bc482e171037095dd156d13a286b3b1c6d59 Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 7 May 2021 21:11:34 +0100 Subject: cyclonev: More validity checking thoughts Signed-off-by: gatecat --- cyclonev/arch.cc | 13 +++++++++++++ cyclonev/arch.h | 5 +++++ cyclonev/archdefs.h | 14 ++++++++++++-- cyclonev/constids.inc | 1 + cyclonev/lab.cc | 23 ++++++++++++++++++----- 5 files changed, 49 insertions(+), 7 deletions(-) diff --git a/cyclonev/arch.cc b/cyclonev/arch.cc index 9aee5a59..fd2f345d 100644 --- a/cyclonev/arch.cc +++ b/cyclonev/arch.cc @@ -308,6 +308,19 @@ void Arch::add_bel_pin(BelId bel, IdString pin, PortType dir, WireId wire) 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 diff --git a/cyclonev/arch.h b/cyclonev/arch.h index 301f19d8..ff006881 100644 --- a/cyclonev/arch.h +++ b/cyclonev/arch.h @@ -393,6 +393,11 @@ struct Arch : BaseArch 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 diff --git a/cyclonev/archdefs.h b/cyclonev/archdefs.h index 5afdd783..31f64a6a 100644 --- a/cyclonev/archdefs.h +++ b/cyclonev/archdefs.h @@ -124,10 +124,19 @@ struct ArchNetInfo { }; +enum CellPinState +{ + PIN_SIG = 0, + PIN_0 = 1, + PIN_1 = 2, + PIN_INV = 3, +}; + struct ArchPinInfo { - // An inverter (INV) has been pushed onto this signal - bool inverted; + // 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; }; @@ -167,6 +176,7 @@ struct ArchCellInfo 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; diff --git a/cyclonev/constids.inc b/cyclonev/constids.inc index fcc723f8..c4af45e6 100644 --- a/cyclonev/constids.inc +++ b/cyclonev/constids.inc @@ -55,6 +55,7 @@ X(MISTRAL_ALUT4) X(MISTRAL_ALUT3) X(MISTRAL_ALUT2) X(MISTRAL_NOT) +X(MISTRAL_BUF) X(MISTRAL_CONST) X(MISTRAL_ALUT_ARITH) diff --git a/cyclonev/lab.cc b/cyclonev/lab.cc index 6f7c5e6d..1f117106 100644 --- a/cyclonev/lab.cc +++ b/cyclonev/lab.cc @@ -195,7 +195,7 @@ 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).inverted; + result.inverted = cell->pin_data.at(port).state == PIN_INV; else result.inverted = false; return result; @@ -259,7 +259,8 @@ void Arch::assign_comb_info(CellInfo *cell) const ++cell->combInfo.lut_input_count; cell->combInfo.lut_in[1] = get_net_or_empty(cell, id_B); [[fallthrough]]; - case ID_MISTRAL_NOT: + 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]]; @@ -272,6 +273,10 @@ void Arch::assign_comb_info(CellInfo *cell) const // 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 @@ -336,9 +341,9 @@ bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const // 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 the LUT is using less than 6 inputs - TODO: is this correct considering all possible LUT - // sharing - bool ef_available = (!luts[i] || luts[i]->combInfo.lut_input_count < 6); + // 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; @@ -383,4 +388,12 @@ bool Arch::is_lab_ctrlset_legal(uint32_t lab) const 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 -- cgit v1.2.3 From 29386822955f072e6472ae4ab42e2bd16858e3c5 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 8 May 2021 10:49:42 +0100 Subject: cyclonev: Rebase update Signed-off-by: gatecat --- cyclonev/archdefs.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cyclonev/archdefs.h b/cyclonev/archdefs.h index 31f64a6a..0f8f5a12 100644 --- a/cyclonev/archdefs.h +++ b/cyclonev/archdefs.h @@ -22,6 +22,7 @@ #include +#include "base_clusterinfo.h" #include "cyclonev.h" #include "idstring.h" @@ -119,6 +120,7 @@ struct PipId typedef IdString DecalId; typedef IdString GroupId; typedef IdString BelBucketId; +typedef IdString ClusterId; struct ArchNetInfo { @@ -165,7 +167,7 @@ struct FFControlSet bool operator!=(const FFControlSet &other) const { return !(*this == other); } }; -struct ArchCellInfo +struct ArchCellInfo : BaseClusterInfo { union { -- cgit v1.2.3 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 From e5e2f7bc62c1cfba2f43ea74199a5a3a379e5de7 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 8 May 2021 13:38:17 +0100 Subject: mistral: Add stub pack/place/route functions Signed-off-by: gatecat --- common/base_arch.h | 2 +- mistral/arch.cc | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++--- mistral/arch.h | 11 ++++++++ mistral/lab.cc | 27 ++++++++++++++++++++ mistral/pack.cc | 35 ++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 mistral/pack.cc diff --git a/common/base_arch.h b/common/base_arch.h index e9cc8cf0..fbafee99 100644 --- a/common/base_arch.h +++ b/common/base_arch.h @@ -75,7 +75,7 @@ typename std::enable_if::value, Tret>::type return_if_mat } template -typename std::enable_if::value, Tret>::type return_if_match(Tret r) +typename std::enable_if::value, Tc>::type return_if_match(Tret r) { NPNR_ASSERT_FALSE("default implementations of cell type and bel bucket range functions only available when the " "respective range types are 'const std::vector&'"); diff --git a/mistral/arch.cc b/mistral/arch.cc index fd2f345d..aa149a3c 100644 --- a/mistral/arch.cc +++ b/mistral/arch.cc @@ -21,6 +21,13 @@ #include "log.h" #include "nextpnr.h" +#include "placer1.h" +#include "placer_heap.h" +#include "router1.h" +#include "router2.h" +#include "timing.h" +#include "util.h" + #include "cyclonev.h" NEXTPNR_NAMESPACE_BEGIN @@ -244,10 +251,6 @@ BelBucketId Arch::getBelBucketForCellType(IdString cell_type) const 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)); @@ -321,6 +324,68 @@ void Arch::assign_default_pinmap(CellInfo *cell) } } +void Arch::assignArchInfo() +{ + for (auto cell : sorted(cells)) { + CellInfo *ci = cell.second; + if (is_comb_cell(ci->type)) + assign_comb_info(ci); + else if (ci->type == id_MISTRAL_FF) + assign_ff_info(ci); + assign_default_pinmap(ci); + } +} + +bool Arch::place() +{ + std::string placer = str_or_default(settings, id("placer"), defaultPlacer); + + if (placer == "heap") { + PlacerHeapCfg cfg(getCtx()); + cfg.ioBufTypes.insert(id_MISTRAL_IO); + cfg.ioBufTypes.insert(id_MISTRAL_IB); + cfg.ioBufTypes.insert(id_MISTRAL_OB); + cfg.cellGroups.emplace_back(); + cfg.cellGroups.back().insert({id_MISTRAL_COMB}); + cfg.cellGroups.back().insert({id_MISTRAL_FF}); + + cfg.beta = 0.5; // TODO: find a good value of beta for sensible ALM spreading + cfg.criticalityExponent = 7; + if (!placer_heap(getCtx(), cfg)) + return false; + } else if (placer == "sa") { + if (!placer1(getCtx(), Placer1Cfg(getCtx()))) + return false; + } else { + log_error("Mistral architecture does not support placer '%s'\n", placer.c_str()); + } + + getCtx()->attrs[getCtx()->id("step")] = std::string("place"); + archInfoToAttributes(); + return true; +} + +bool Arch::route() +{ + assign_budget(getCtx(), true); + + lab_pre_route(); + + std::string router = str_or_default(settings, id("router"), defaultRouter); + bool result; + if (router == "router1") { + result = router1(getCtx(), Router1Cfg(getCtx())); + } else if (router == "router2") { + router2(getCtx(), Router2Cfg(getCtx())); + result = true; + } else { + log_error("Mistral architecture does not support router '%s'\n", router.c_str()); + } + getCtx()->attrs[getCtx()->id("step")] = std::string("route"); + archInfoToAttributes(); + return result; +} + #ifdef WITH_HEAP const std::string Arch::defaultPlacer = "heap"; #else diff --git a/mistral/arch.h b/mistral/arch.h index ff006881..dd684dfe 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -214,6 +214,7 @@ struct ArchRanges : BaseArchRanges using AllBelsRangeT = const std::vector &; using TileBelsRangeT = std::vector; using BelPinsRangeT = std::vector; + using CellBelPinRangeT = const std::vector &; // Wires using AllWiresRangeT = AllWireRange; using DownhillPipRangeT = UpDownhillPipRange; @@ -315,11 +316,17 @@ struct Arch : BaseArch // ------------------------------------------------- + const std::vector &getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const override + { + return cell_info->pin_data.at(pin).bel_pins; + } + bool isValidBelForCellType(IdString cell_type, BelId bel) const override; BelBucketId getBelBucketForCellType(IdString cell_type) const override; // ------------------------------------------------- + void assignArchInfo() override; bool pack() override; bool place() override; bool route() override; @@ -350,6 +357,10 @@ struct Arch : BaseArch void assign_comb_info(CellInfo *cell) const; // lab.cc void assign_ff_info(CellInfo *cell) const; // lab.cc + void lab_pre_route(); // lab.cc + void assign_control_sets(uint32_t lab); // lab.cc + void reassign_alm_inputs(uint32_t lab, uint8_t alm); // lab.cc + // ------------------------------------------------- bool is_io_cell(IdString cell_type) const; // io.cc diff --git a/mistral/lab.cc b/mistral/lab.cc index 1f117106..f01c6e95 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -388,6 +388,33 @@ bool Arch::is_lab_ctrlset_legal(uint32_t lab) const return true; } +void Arch::lab_pre_route() +{ + for (uint32_t lab = 0; lab < labs.size(); lab++) { + assign_control_sets(lab); + for (uint8_t alm = 0; alm < 10; alm++) { + reassign_alm_inputs(lab, alm); + } + } +} + +void Arch::assign_control_sets(uint32_t lab) +{ + // TODO: set up reservations for checkPipAvailForNet for control set signals + // This will be needed because clock and CE are routed together and must be kept together, there isn't free choice + // e.g. CLK0 & ENA0 must be use for one control set, and CLK1 & ENA1 for another, they can't be mixed and matched +} + +void Arch::reassign_alm_inputs(uint32_t lab, uint8_t alm) +{ + // TODO: based on the usage of LUTs inside the ALM, set up cell-bel pin map for the combinational cells in the ALM + // so that each physical bel pin is only used for one net; and the logical functions can be implemented correctly. + // This function should also insert route-through LUTs to legalise flipflop inputs as needed. + // TODO: in the future, as well as the reassignment here we will also have pseudo PIPs in front of the ALM so that + // the router can permute LUTs for routeability; too. Here we will need to lock out some of those PIPs depending on + // the usage of the ALM, as not all inputs are always interchangeable. +} + // 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 = { diff --git a/mistral/pack.cc b/mistral/pack.cc new file mode 100644 index 00000000..78a000d9 --- /dev/null +++ b/mistral/pack.cc @@ -0,0 +1,35 @@ +/* + * 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 + +bool Arch::pack() +{ + // TODO: + // - Insert constant driver LUTs + // - Fold constants and inverters into cell pins + // - Constrain IO + return true; +} + +NEXTPNR_NAMESPACE_END \ No newline at end of file -- cgit v1.2.3 From d38ff142644479d9760d32f17bec291e96ab100c Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 8 May 2021 14:30:29 +0100 Subject: mistral: Working on ALM input assignment Signed-off-by: gatecat --- mistral/arch.h | 3 +- mistral/lab.cc | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/mistral/arch.h b/mistral/arch.h index dd684dfe..3d939c0d 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -43,7 +43,8 @@ struct ALMInfo // Pointers to bels std::array lut_bels; std::array ff_bels; - // TODO: ALM configuration (L5/L6 mode, LUT input permutation, etc) + + bool l6_mode = false; }; struct LABInfo diff --git a/mistral/lab.cc b/mistral/lab.cc index f01c6e95..a8dd8dd5 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -405,14 +405,129 @@ void Arch::assign_control_sets(uint32_t lab) // e.g. CLK0 & ENA0 must be use for one control set, and CLK1 & ENA1 for another, they can't be mixed and matched } +namespace { +// Gets the name of logical LUT pin i for a given cell +static IdString get_lut_pin(CellInfo *cell, int i) +{ + const std::array log_pins{id_A, id_B, id_C, id_D, id_E, id_F}; + const std::array log_pins_arith{id_A, id_B, id_C, id_D0, id_D1}; + return (cell->type == id_MISTRAL_ALUT_ARITH) ? log_pins_arith.at(i) : log_pins.at(i); +} + +static void assign_lut6_inputs(CellInfo *cell, int lut) +{ + std::array phys_pins{id_A, id_B, id_C, id_D, (lut == 1) ? id_E1 : id_E0, (lut == 1) ? id_F1 : id_F0}; + int phys_idx = 0; + for (int i = 0; i < 6; i++) { + IdString log = get_lut_pin(cell, i); + if (!cell->ports.count(log) || cell->ports.at(log).net == nullptr) + continue; + cell->pin_data[log].bel_pins.clear(); + cell->pin_data[log].bel_pins.push_back(phys_pins.at(++phys_idx)); + } +} +} // namespace + void Arch::reassign_alm_inputs(uint32_t lab, uint8_t alm) { - // TODO: based on the usage of LUTs inside the ALM, set up cell-bel pin map for the combinational cells in the ALM + // Based on the usage of LUTs inside the ALM, set up cell-bel pin map for the combinational cells in the ALM // so that each physical bel pin is only used for one net; and the logical functions can be implemented correctly. // This function should also insert route-through LUTs to legalise flipflop inputs as needed. + auto &alm_data = labs.at(lab).alms.at(alm); + alm_data.l6_mode = false; + std::array 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])}; + + for (int i = 0; i < 2; i++) { + // Currently we treat LUT6s as a special case, as they never share inputs + if (luts[i] != nullptr && luts[i]->type == id_MISTRAL_ALUT6) { + alm_data.l6_mode = true; + NPNR_ASSERT(luts[1 - i] == nullptr); // only allow one LUT6 per ALM and no other LUTs + assign_lut6_inputs(luts[i], i); + } + } + + if (!alm_data.l6_mode) { + // In L5 mode; which is what we use in this case + // - A and B are shared + // - C, E0, and F0 are exclusive to the top LUT5 secion + // - D, E1, and F1 are exclusive to the bottom LUT5 section + // First find up to two shared inputs + std::unordered_map shared_nets; + if (luts[0] && luts[1]) { + for (int i = 0; i < luts[0]->combInfo.lut_input_count; i++) { + for (int j = 0; j < luts[1]->combInfo.lut_input_count; j++) { + if (luts[0]->combInfo.lut_in[i] == nullptr) + continue; + if (luts[0]->combInfo.lut_in[i] != luts[1]->combInfo.lut_in[j]) + continue; + IdString net = luts[0]->combInfo.lut_in[i]->name; + if (shared_nets.count(net)) + continue; + int idx = int(shared_nets.size()); + shared_nets[net] = idx; + if (shared_nets.size() >= 2) + goto shared_search_done; + } + } + shared_search_done:; + } + // A and B can be used for half-specific nets if not assigned to shared nets + bool a_avail = shared_nets.size() == 0, b_avail = shared_nets.size() <= 1; + // Do the actual port assignment + for (int i = 0; i < 2; i++) { + if (!luts[i]) + continue; + // Work out which physical ports are available + std::vector avail_phys_ports; + avail_phys_ports.push_back((i == 1) ? id_D : id_C); + if (b_avail) + avail_phys_ports.push_back(id_B); + if (a_avail) + avail_phys_ports.push_back(id_A); + // In arithmetic mode, Ei can only be used for D0 and Fi can only be used for D1 + if (!luts[i]->combInfo.is_carry) { + avail_phys_ports.push_back((i == 1) ? id_E1 : id_E0); + avail_phys_ports.push_back((i == 1) ? id_F1 : id_F0); + } + int phys_idx = 0; + + for (int j = 0; j < luts[i]->combInfo.lut_input_count; j++) { + IdString log = get_lut_pin(luts[i], j); + auto &bel_pins = luts[i]->pin_data[log].bel_pins; + bel_pins.clear(); + + NetInfo *net = get_net_or_empty(luts[i], log); + if (net == nullptr) { + // Disconnected inputs don't need to be allocated a pin, because the router won't be routing these + continue; + } else if (shared_nets.count(net->name)) { + // This pin is to be allocated one of the shared nets + bel_pins.push_back(shared_nets.at(net->name) ? id_B : id_A); + } else if (log == id_D0) { + // Arithmetic + bel_pins.push_back((i == 1) ? id_E1 : id_E0); // reserved + } else if (log == id_D1) { + bel_pins.push_back((i == 1) ? id_F1 : id_F0); // reserved + } else { + // Allocate from the general pool of available physical pins + IdString phys = avail_phys_ports.at(phys_idx++); + bel_pins.push_back(phys); + // Mark A/B unavailable for the other LUT, if needed + if (phys == id_A) + a_avail = false; + else if (phys == id_B) + b_avail = false; + } + } + } + } + // TODO: in the future, as well as the reassignment here we will also have pseudo PIPs in front of the ALM so that // the router can permute LUTs for routeability; too. Here we will need to lock out some of those PIPs depending on // the usage of the ALM, as not all inputs are always interchangeable. + // Get cells into an array for fast access } // This default cell-bel pin mapping is used to provide estimates during placement only. It will have errors and -- cgit v1.2.3 From 1b729d90d0cd7c175da0162e1ba4f0aa6ef48ff0 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 8 May 2021 19:28:11 +0100 Subject: mistral: Add the 'pin style' stuff based on Nexus Signed-off-by: gatecat --- mistral/arch.h | 45 ++++++++++++++++++++++++++++++++++++++ mistral/pins.cc | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 mistral/pins.cc diff --git a/mistral/arch.h b/mistral/arch.h index 3d939c0d..d4f6bd5e 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -225,6 +225,45 @@ struct ArchRanges : BaseArchRanges using AllPipsRangeT = AllPipRange; }; +// This enum captures different 'styles' of cell pins +// This is a combination of the modes available for a pin (tied high, low or inverted) +// and the default value to set it to not connected +enum CellPinStyle +{ + PINOPT_NONE = 0x0, // no options, just signal as-is + PINOPT_LO = 0x1, // can be tied low + PINOPT_HI = 0x2, // can be tied high + PINOPT_INV = 0x4, // can be inverted + + PINOPT_LOHI = 0x3, // can be tied low or high + PINOPT_LOHIINV = 0x7, // can be tied low or high; or inverted + + PINOPT_MASK = 0x7, + + PINDEF_NONE = 0x00, // leave disconnected + PINDEF_0 = 0x10, // connect to 0 if not used + PINDEF_1 = 0x20, // connect to 1 if not used + + PINDEF_MASK = 0x30, + + PINGLB_CLK = 0x100, // pin is a 'clock' for global purposes + + PINGLB_MASK = 0x100, + + PINSTYLE_NONE = 0x000, // default + + PINSTYLE_COMB = 0x017, // combinational signal, defaults low, can be inverted and tied + PINSTYLE_CLK = 0x107, // CLK type signal, invertible and defaults to disconnected + PINSTYLE_CE = 0x027, // CE type signal, invertible and defaults to enabled + PINSTYLE_RST = 0x017, // RST type signal, invertible and defaults to not reset + PINSTYLE_DEDI = 0x000, // dedicated signals, leave alone + PINSTYLE_INP = 0x001, // general inputs, no inversion/tieing but defaults low + PINSTYLE_PU = 0x022, // signals that float high and default high + + PINSTYLE_CARRY = 0x001, // carry chains can be floating or 0? + +}; + struct Arch : BaseArch { ArchArgs args; @@ -410,6 +449,12 @@ struct Arch : BaseArch void assign_default_pinmap(CellInfo *cell); static const std::unordered_map comb_pinmap; + + // ------------------------------------------------- + + typedef std::unordered_map CellPinsData; // pins.cc + static const std::unordered_map cell_pins_db; // pins.cc + CellPinStyle get_cell_pin_style(const CellInfo *cell, IdString port) const; // pins.cc }; NEXTPNR_NAMESPACE_END diff --git a/mistral/pins.cc b/mistral/pins.cc new file mode 100644 index 00000000..d0ce642e --- /dev/null +++ b/mistral/pins.cc @@ -0,0 +1,67 @@ +/* + * 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 + +const std::unordered_map Arch::cell_pins_db = { + // For combinational cells, inversion and tieing can be implemented by manipulating the LUT function + {id_MISTRAL_ALUT2, {{{}, PINSTYLE_COMB}}}, + {id_MISTRAL_ALUT3, {{{}, PINSTYLE_COMB}}}, + {id_MISTRAL_ALUT4, {{{}, PINSTYLE_COMB}}}, + {id_MISTRAL_ALUT5, {{{}, PINSTYLE_COMB}}}, + {id_MISTRAL_ALUT6, {{{}, PINSTYLE_COMB}}}, + {id_MISTRAL_ALUT_ARITH, + {// Leave carry chain alone, other than disconnecting a ground constant + {id_CI, PINSTYLE_CARRY}, + {{}, PINSTYLE_COMB}}}, + {id_MISTRAL_FF, + { + {id_CLK, PINSTYLE_CLK}, + {id_ENA, PINSTYLE_CE}, + {id_ACLR, PINSTYLE_RST}, + {id_SCLR, PINSTYLE_RST}, + {id_SLOAD, PINSTYLE_RST}, + {id_SDATA, PINSTYLE_DEDI}, + {id_DATAIN, PINSTYLE_INP}, + }}, +}; + +CellPinStyle Arch::get_cell_pin_style(const CellInfo *cell, IdString port) const +{ + // Look up the pin style in the cell database + auto fnd_cell = cell_pins_db.find(cell->type); + if (fnd_cell == cell_pins_db.end()) + return PINSTYLE_NONE; + auto fnd_port = fnd_cell->second.find(port); + if (fnd_port != fnd_cell->second.end()) + return fnd_port->second; + // If there isn't an exact port match, then the empty IdString + // represents a wildcard default match + auto fnd_default = fnd_cell->second.find({}); + if (fnd_default != fnd_cell->second.end()) + return fnd_default->second; + + return PINSTYLE_NONE; +} + +NEXTPNR_NAMESPACE_END \ No newline at end of file -- cgit v1.2.3 From 3fc5396063ca0940383b1d923eb77750515542a3 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 8 May 2021 20:33:14 +0100 Subject: mistral: Working on FF validity checking Signed-off-by: gatecat --- mistral/archdefs.h | 1 + mistral/lab.cc | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/mistral/archdefs.h b/mistral/archdefs.h index 0f8f5a12..2ae1bccb 100644 --- a/mistral/archdefs.h +++ b/mistral/archdefs.h @@ -124,6 +124,7 @@ typedef IdString ClusterId; struct ArchNetInfo { + bool is_global = false; }; enum CellPinState diff --git a/mistral/lab.cc b/mistral/lab.cc index a8dd8dd5..eab4d54d 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -17,6 +17,7 @@ * */ +#include "design_utils.h" #include "log.h" #include "nextpnr.h" #include "util.h" @@ -382,14 +383,100 @@ bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const return true; } +namespace { +bool check_assign_sig(ControlSig &sig_set, const ControlSig &sig) +{ + if (sig.net == nullptr) { + return true; + } else if (sig_set == sig) { + return true; + } else if (sig_set.net == nullptr) { + sig_set = sig; + return true; + } else { + return false; + } +}; + +template bool check_assign_sig(std::array &sig_set, const ControlSig &sig) +{ + if (sig.net == nullptr) + return true; + for (size_t i = 0; i < N; i++) + if (sig_set[i] == sig) { + return true; + } else if (sig_set[i].net == nullptr) { + sig_set[i] = sig; + return true; + } + return false; +}; +}; // namespace + bool Arch::is_lab_ctrlset_legal(uint32_t lab) const { - // TODO + // Strictly speaking the constraint is up to 2 unique CLK and 3 CLK+ENA pairs. For now we simplify this to 1 CLK and + // 3 ENA though. + ControlSig clk, sload, sclr; + std::array aclr{}; + std::array ena{}; + + for (uint8_t alm = 0; alm < 10; alm++) { + for (uint8_t i = 0; i < 4; i++) { + const CellInfo *ff = getBoundBelCell(labs.at(lab).alms.at(alm).ff_bels.at(i)); + if (ff == nullptr) + continue; + + if (!check_assign_sig(clk, ff->ffInfo.ctrlset.clk)) + return false; + if (!check_assign_sig(sload, ff->ffInfo.ctrlset.sload)) + return false; + if (!check_assign_sig(sclr, ff->ffInfo.ctrlset.sclr)) + return false; + if (!check_assign_sig(aclr, ff->ffInfo.ctrlset.aclr)) + return false; + if (!check_assign_sig(ena, ff->ffInfo.ctrlset.ena)) + return false; + } + } + + // Check for overuse of the shared, LAB-wide datain signals + std::array datain{}; + if (clk.net != nullptr && !clk.net->is_global) + if (!check_assign_sig(datain[0], clk)) // CLK only needs DATAIN[0] if it's not global + return false; + if (!check_assign_sig(datain[1], sload)) + return false; + if (!check_assign_sig(datain[3], sclr)) + return false; + for (const auto &aclr_sig : aclr) { + // Check both possibilities that ACLR can map to + // TODO: ACLR could be global, too + if (check_assign_sig(datain[3], aclr_sig)) + continue; + if (check_assign_sig(datain[2], aclr_sig)) + continue; + // Failed to find any free ACLR-capable DATAIN + return false; + } + for (const auto &ena_sig : ena) { + // Check all 3 possibilities that ACLR can map to + // TODO: ACLR could be global, too + if (check_assign_sig(datain[2], ena_sig)) + continue; + if (check_assign_sig(datain[3], ena_sig)) + continue; + if (check_assign_sig(datain[0], ena_sig)) + continue; + // Failed to find any free ENA-capable DATAIN + return false; + } return true; } void Arch::lab_pre_route() { + log_info("Preparing LABs for routing...\n"); for (uint32_t lab = 0; lab < labs.size(); lab++) { assign_control_sets(lab); for (uint8_t alm = 0; alm < 10; alm++) { @@ -524,6 +611,34 @@ void Arch::reassign_alm_inputs(uint32_t lab, uint8_t alm) } } + // FF route-through insertion + for (int i = 0; i < 2; i++) { + // FF route-through will never be inserted if LUT is used + if (luts[i]) + continue; + for (int j = 0; j < 2; j++) { + CellInfo *ff = ffs[i * 2 + j]; + if (!ff || !ff->ffInfo.datain) + continue; + CellInfo *rt_lut = createCell(id(stringf("%s$ROUTETHRU", nameOf(ff))), id_MISTRAL_BUF); + rt_lut->addInput(id_A); + rt_lut->addOutput(id_Q); + // Disconnect the original data input to the FF, and connect it to the route-thru LUT instead + NetInfo *datain = get_net_or_empty(ff, id_DATAIN); + disconnect_port(getCtx(), ff, id_DATAIN); + connect_port(getCtx(), datain, rt_lut, id_A); + connect_ports(getCtx(), rt_lut, id_Q, ff, id_DATAIN); + // Assign route-thru LUT physical ports, input goes to the first half-specific input + NPNR_ASSERT(!alm_data.l6_mode); + rt_lut->pin_data[id_A].bel_pins.push_back(i ? id_D : id_C); + rt_lut->pin_data[id_Q].bel_pins.push_back(id_COMBOUT); + assign_comb_info(rt_lut); + // Place the route-thru LUT at the relevant combinational bel + bindBel(alm_data.lut_bels[i], rt_lut, STRENGTH_STRONG); + break; + } + } + // TODO: in the future, as well as the reassignment here we will also have pseudo PIPs in front of the ALM so that // the router can permute LUTs for routeability; too. Here we will need to lock out some of those PIPs depending on // the usage of the ALM, as not all inputs are always interchangeable. -- cgit v1.2.3 From 595b354184afdbdbe6e4daade739d79db8fcee42 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 9 May 2021 10:17:42 +0100 Subject: mistral: Add some packing logic based on nexus Signed-off-by: gatecat --- mistral/archdefs.cc | 33 +++++++++++ mistral/archdefs.h | 2 + mistral/constids.inc | 8 ++- mistral/pack.cc | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 mistral/archdefs.cc diff --git a/mistral/archdefs.cc b/mistral/archdefs.cc new file mode 100644 index 00000000..8a2bbe7a --- /dev/null +++ b/mistral/archdefs.cc @@ -0,0 +1,33 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +CellPinState ArchCellInfo::get_pin_state(IdString pin) const +{ + auto fnd = pin_data.find(pin); + if (fnd != pin_data.end()) + return fnd->second.state; + else + return PIN_SIG; +} + +NEXTPNR_NAMESPACE_END diff --git a/mistral/archdefs.h b/mistral/archdefs.h index 2ae1bccb..ab00cb26 100644 --- a/mistral/archdefs.h +++ b/mistral/archdefs.h @@ -192,6 +192,8 @@ struct ArchCellInfo : BaseClusterInfo }; std::unordered_map pin_data; + + CellPinState get_pin_state(IdString pin) const; }; NEXTPNR_NAMESPACE_END diff --git a/mistral/constids.inc b/mistral/constids.inc index c4af45e6..c17d9f89 100644 --- a/mistral/constids.inc +++ b/mistral/constids.inc @@ -58,6 +58,9 @@ X(MISTRAL_NOT) X(MISTRAL_BUF) X(MISTRAL_CONST) X(MISTRAL_ALUT_ARITH) +X(LUT) +X(LUT0) +X(LUT1) X(D0) X(D1) @@ -65,4 +68,7 @@ X(CI) X(CO) X(SO) -X(WIRE) \ No newline at end of file +X(WIRE) + +X(GND) +X(VCC) diff --git a/mistral/pack.cc b/mistral/pack.cc index 78a000d9..be0128fe 100644 --- a/mistral/pack.cc +++ b/mistral/pack.cc @@ -17,18 +17,176 @@ * */ +#include "design_utils.h" #include "log.h" #include "nextpnr.h" #include "util.h" NEXTPNR_NAMESPACE_BEGIN +namespace { +struct MistralPacker +{ + MistralPacker(Context *ctx) : ctx(ctx){}; + Context *ctx; + + NetInfo *gnd_net, *vcc_net; + + void init_constant_nets() + { + CellInfo *gnd_drv = ctx->createCell(ctx->id("$PACKER_GND_DRV"), id_MISTRAL_CONST); + gnd_drv->params[id_LUT] = 0; + gnd_drv->addOutput(id_Q); + CellInfo *vcc_drv = ctx->createCell(ctx->id("$PACKER_VCC_DRV"), id_MISTRAL_CONST); + vcc_drv->params[id_LUT] = 1; + vcc_drv->addOutput(id_Q); + gnd_net = ctx->createNet(ctx->id("$PACKER_GND_NET")); + vcc_net = ctx->createNet(ctx->id("$PACKER_VCC_NET")); + connect_port(ctx, gnd_net, gnd_drv, id_Q); + connect_port(ctx, vcc_net, vcc_drv, id_Q); + } + + CellPinState get_pin_needed_muxval(CellInfo *cell, IdString port) + { + NetInfo *net = get_net_or_empty(cell, port); + if (net == nullptr || net->driver.cell == nullptr) { + // Pin is disconnected + // If a mux value exists already, honour it + CellPinState exist_mux = cell->get_pin_state(port); + if (exist_mux != PIN_SIG) + return exist_mux; + // Otherwise, look up the default value and use that + CellPinStyle pin_style = ctx->get_cell_pin_style(cell, port); + if ((pin_style & PINDEF_MASK) == PINDEF_0) + return PIN_0; + else if ((pin_style & PINDEF_MASK) == PINDEF_1) + return PIN_1; + else + return PIN_SIG; + } + // Look to see if the driver is an inverter or constant + IdString drv_type = net->driver.cell->type; + if (drv_type == id_MISTRAL_NOT) + return PIN_INV; + else if (drv_type == id_GND) + return PIN_0; + else if (drv_type == id_VCC) + return PIN_1; + else + return PIN_SIG; + } + + void uninvert_port(CellInfo *cell, IdString port) + { + // Rewire a port so it is driven by the input to an inverter + NetInfo *net = get_net_or_empty(cell, port); + NPNR_ASSERT(net != nullptr && net->driver.cell != nullptr && net->driver.cell->type == id_MISTRAL_NOT); + CellInfo *inv = net->driver.cell; + disconnect_port(ctx, cell, port); + + NetInfo *inv_a = get_net_or_empty(inv, id_A); + if (inv_a != nullptr) { + connect_port(ctx, inv_a, cell, port); + } + } + + void process_inv_constants(CellInfo *cell) + { + // TODO: we might need to create missing inputs here in some cases so we can tie them to the correct constant? + // Fold inverters and constants into a cell + for (auto &port : cell->ports) { + // Iterate over all inputs + if (port.second.type != PORT_IN) + continue; + IdString port_name = port.first; + + CellPinState req_mux = get_pin_needed_muxval(cell, port_name); + if (req_mux == PIN_SIG) { + // No special setting required, ignore + continue; + } + + CellPinStyle pin_style = ctx->get_cell_pin_style(cell, port_name); + + if (req_mux == PIN_INV) { + // Pin is inverted. If there is a hard inverter; then use it + if (pin_style & PINOPT_INV) { + uninvert_port(cell, port_name); + cell->pin_data[port_name].state = PIN_INV; + } + } else if (req_mux == PIN_0 || req_mux == PIN_1) { + // Pin is tied to a constant + // If there is a hard constant option; use it + if ((pin_style & int(req_mux)) == req_mux) { + disconnect_port(ctx, cell, port_name); + cell->pin_data[port_name].state = req_mux; + } else { + disconnect_port(ctx, cell, port_name); + // There is no hard constant, we need to connect it to the relevant soft-constant net + connect_port(ctx, (req_mux == PIN_1) ? vcc_net : gnd_net, cell, port_name); + } + } + } + } + + void trim_design() + { + // Remove unused inverters and high/low drivers + std::vector trim_cells; + std::vector trim_nets; + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_MISTRAL_NOT && ci->type != id_GND && ci->type != id_VCC) + continue; + IdString port = (ci->type == id_MISTRAL_NOT) ? id_Q : ci->type; + NetInfo *out = get_net_or_empty(ci, port); + if (out == nullptr) { + trim_cells.push_back(ci->name); + continue; + } + if (!out->users.empty()) + continue; + + disconnect_port(ctx, ci, id_A); + + trim_cells.push_back(ci->name); + trim_nets.push_back(out->name); + } + + for (IdString rem_net : trim_nets) + ctx->nets.erase(rem_net); + for (IdString rem_cell : trim_cells) + ctx->cells.erase(rem_cell); + } + + void pack_constants() + { + // Iterate through cells + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + // Skip certain cells at this point + if (ci->type != id_MISTRAL_NOT && ci->type != id_GND && ci->type != id_VCC) + process_inv_constants(cell.second); + } + // Remove superfluous inverters and constant drivers + trim_design(); + } + + void run() + { + init_constant_nets(); + pack_constants(); + } +}; +}; // namespace bool Arch::pack() { // TODO: - // - Insert constant driver LUTs - // - Fold constants and inverters into cell pins // - Constrain IO + + MistralPacker packer(getCtx()); + packer.run(); + return true; } -- cgit v1.2.3 From 96f16c8635b37aa58f275e63df5172902bedf8f5 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 9 May 2021 12:07:24 +0100 Subject: mistral: Add a basic QSF parser Signed-off-by: gatecat --- mistral/arch.h | 4 + mistral/constids.inc | 2 + mistral/main.cc | 9 +- mistral/qsf.cc | 281 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 mistral/qsf.cc diff --git a/mistral/arch.h b/mistral/arch.h index d4f6bd5e..b818f001 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -455,6 +455,10 @@ struct Arch : BaseArch typedef std::unordered_map CellPinsData; // pins.cc static const std::unordered_map cell_pins_db; // pins.cc CellPinStyle get_cell_pin_style(const CellInfo *cell, IdString port) const; // pins.cc + + // List of IO constraints, used by QSF parser + std::unordered_map> io_attr; + void read_qsf(std::istream &in); // qsf.cc }; NEXTPNR_NAMESPACE_END diff --git a/mistral/constids.inc b/mistral/constids.inc index c17d9f89..342ca353 100644 --- a/mistral/constids.inc +++ b/mistral/constids.inc @@ -72,3 +72,5 @@ X(WIRE) X(GND) X(VCC) + +X(LOC) \ No newline at end of file diff --git a/mistral/main.cc b/mistral/main.cc index 702b4b7e..d5816693 100644 --- a/mistral/main.cc +++ b/mistral/main.cc @@ -49,6 +49,7 @@ 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)"); + specific.add_options()("qsf", po::value(), "path to QSF constraints file"); return specific; } @@ -74,7 +75,13 @@ std::unique_ptr MistralCommandHandler::createContext(std::unordered_map void MistralCommandHandler::customAfterLoad(Context *ctx) { - // TODO: qsf parsing + if (vm.count("qsf")) { + std::string filename = vm["qsf"].as(); + std::ifstream in(filename); + if (!in) + log_error("Failed to open input QSF file %s.\n", filename.c_str()); + ctx->read_qsf(in); + } } int main(int argc, char *argv[]) diff --git a/mistral/qsf.cc b/mistral/qsf.cc new file mode 100644 index 00000000..3f201aa1 --- /dev/null +++ b/mistral/qsf.cc @@ -0,0 +1,281 @@ +/* + * 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" + +#include + +NEXTPNR_NAMESPACE_BEGIN + +namespace { + +struct QsfOption +{ + std::string name; // name, excluding the initial '-' + int arg_count; // number of arguments that follow the option + bool required; // error out if this option isn't passed +}; + +typedef std::unordered_map> option_map_t; + +struct QsfCommand +{ + std::string name; // name of the command + std::vector options; // list of "-options" + int pos_arg_count; // number of positional arguments expected to follow the command, -1 for any + std::function &pos_args)> func; +}; + +void set_location_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector &pos_args) +{ + ctx->io_attr[ctx->id(options.at("to").at(0))][id_LOC] = pos_args.at(0); +} + +void set_instance_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector &pos_args) +{ + ctx->io_attr[ctx->id(options.at("to").at(0))][id_LOC] = pos_args.at(0); +} + +void set_global_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector &pos_args) +{ + // TODO +} + +static const std::vector commands = { + {"set_location_assignment", {{"to", 1, true}}, 1, set_location_assignment_cmd}, + {"set_instance_assignment", + {{"to", 1, true}, {"name", 1, true}, {"section_id", 1, false}}, + 1, + set_instance_assignment_cmd}, + {"set_global_assignment", + {{"name", 1, true}, {"section_id", 1, false}, {"rise", 0, false}, {"fall", 0, false}}, + 1, + set_global_assignment_cmd}, +}; + +struct QsfParser +{ + std::string buf; + int pos = 0; + int lineno = 0; + Context *ctx; + + QsfParser(const std::string &buf, Context *ctx) : buf(buf), ctx(ctx){}; + + inline bool eof() const { return pos == int(buf.size()); } + + inline char peek() const { return buf.at(pos); } + + inline char get() + { + char c = buf.at(pos++); + if (c == '\n') + ++lineno; + return c; + } + + std::string get(int n) + { + std::string s = buf.substr(pos, n); + pos += n; + return s; + } + + // If next char matches c, take it from the stream and return true + bool check_get(char c) + { + if (peek() == c) { + get(); + return true; + } else { + return false; + } + } + + // If next char matches any in chars, take it from the stream and return true + bool check_get_any(const std::string &chrs) + { + char c = peek(); + if (chrs.find(c) != std::string::npos) { + get(); + return true; + } else { + return false; + } + } + + inline void skip_blank(bool nl = false) + { + while (!eof() && check_get_any(nl ? " \t\n\r" : " \t")) + ; + } + + // Return true if end of line (or file) + inline bool skip_check_eol() + { + skip_blank(false); + if (eof()) + return true; + char c = peek(); + // Comments count as end of line + if (c == '#') { + get(); + while (!eof() && peek() != '\n' && peek() != '\r') + get(); + return true; + } + if (c == ';') { + // Forced end of line + get(); + return true; + } + return (c == '\n' || c == '\r'); + } + + // We need to distinguish between quoted and unquoted strings, the former don't count as options + struct StringVal + { + std::string str; + bool is_quoted = false; + }; + + inline StringVal get_str() + { + StringVal s; + skip_blank(false); + if (eof()) + return {"", false}; + + bool in_quotes = false, in_braces = false, escaped = false; + + char c = get(); + + if (c == '"') { + in_quotes = true; + s.is_quoted = true; + } else if (c == '{') { + in_braces = true; + s.is_quoted = true; + } else { + s.str += c; + } + + while (!eof()) { + char c = peek(); + if (!in_quotes && !in_braces && !escaped && (std::isblank(c) || c == '\n' || c == '\r')) { + break; + } + get(); + if (escaped) { + s.str += c; + escaped = false; + } else if ((in_quotes && c == '"') || (in_braces && c == '}')) { + break; + } else if (c == '\\') { + escaped = true; + } else { + s.str += c; + } + } + + return s; + } + + std::vector get_arguments() + { + std::vector args; + while (!skip_check_eol()) { + args.push_back(get_str()); + } + skip_blank(true); + return args; + } + + void evaluate(const std::vector &args) + { + if (args.empty()) + return; + auto cmd_name = args.at(0).str; + auto fnd_cmd = + std::find_if(commands.begin(), commands.end(), [&](const QsfCommand &c) { return c.name == cmd_name; }); + if (fnd_cmd == commands.end()) { + log_warning("Ignoring unknown command '%s' (line %d)\n", cmd_name.c_str(), lineno); + return; + } + option_map_t opt; + std::vector pos_args; + for (size_t i = 1; i < args.size(); i++) { + auto arg = args.at(i); + if (arg.str.at(0) == '-' && !arg.is_quoted) { + for (auto &opt_data : fnd_cmd->options) { + if (arg.str.compare(1, std::string::npos, opt_data.name) != 0) + continue; + opt[opt_data.name]; // create empty entry, even if 0 arguments + for (int j = 0; j < opt_data.arg_count; j++) { + ++i; + if (i >= args.size()) + log_error("Unexpected end of argument list to option '%s' (line %d)\n", arg.str.c_str(), + lineno); + opt[opt_data.name].push_back(args.at(i).str); + } + goto done; + } + log_error("Unknown option '%s' to command '%s' (line %d)\n", arg.str.c_str(), cmd_name.c_str(), lineno); + done:; + } else { + // positional argument + pos_args.push_back(arg.str); + } + } + // Check positional argument count + if (int(pos_args.size()) != fnd_cmd->pos_arg_count && fnd_cmd->pos_arg_count != -1) { + log_error("Expected %d positional arguments to command '%s', got %d (line %d)\n", fnd_cmd->pos_arg_count, + cmd_name.c_str(), int(pos_args.size()), lineno); + } + // Check required options + for (auto &opt_data : fnd_cmd->options) { + if (opt_data.required && !opt.count(opt_data.name)) + log_error("Missing required option '%s' to command '%s' (line %d)\n", opt_data.name.c_str(), + cmd_name.c_str(), lineno); + } + // Execute + fnd_cmd->func(ctx, opt, pos_args); + } + + void operator()() + { + while (!eof()) { + skip_blank(true); + auto args = get_arguments(); + evaluate(args); + } + } +}; + +}; // namespace + +void Arch::read_qsf(std::istream &in) +{ + std::string buf(std::istreambuf_iterator(in), {}); + QsfParser(buf, getCtx())(); +} + +NEXTPNR_NAMESPACE_END \ No newline at end of file -- cgit v1.2.3 From 5d191f8297e338e5ac678bdbdfcb176a2a7a8cc3 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 9 May 2021 13:16:22 +0100 Subject: mistral: Add IO packing Signed-off-by: gatecat --- mistral/arch.cc | 18 ++++++++-- mistral/arch.h | 5 ++- mistral/io.cc | 8 +++++ mistral/pack.cc | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- mistral/qsf.cc | 2 +- 5 files changed, 135 insertions(+), 8 deletions(-) diff --git a/mistral/arch.cc b/mistral/arch.cc index aa149a3c..592eddff 100644 --- a/mistral/arch.cc +++ b/mistral/arch.cc @@ -73,9 +73,6 @@ Arch::Arch(ArchArgs args) case CycloneV::block_type_t::LAB: create_lab(x, y); break; - case CycloneV::block_type_t::GPIO: - create_gpio(x, y); - break; default: continue; } @@ -83,6 +80,10 @@ Arch::Arch(ArchArgs args) } } + for (auto gpio_pos : cyclonev->gpio_get_pos()) { + create_gpio(CycloneV::pos2x(gpio_pos), CycloneV::pos2y(gpio_pos)); + } + // This import takes about 5s, perhaps long term we can speed it up, e.g. defer to Mistral more... log_info("Initialising routing graph...\n"); int pip_count = 0; @@ -251,6 +252,17 @@ BelBucketId Arch::getBelBucketForCellType(IdString cell_type) const return cell_type; } +BelId Arch::bel_by_block_idx(int x, int y, IdString type, int block_index) const +{ + auto &bels = bels_by_tile.at(pos2idx(x, y)); + for (size_t i = 0; i < bels.size(); i++) { + auto &bel_data = bels.at(i); + if (bel_data.type == type && bel_data.block_index == block_index) + return BelId(CycloneV::xy2pos(x, y), i); + } + return BelId(); +} + BelId Arch::add_bel(int x, int y, IdString name, IdString type) { auto &bels = bels_by_tile.at(pos2idx(x, y)); diff --git a/mistral/arch.h b/mistral/arch.h index b818f001..790c6782 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -316,6 +316,8 @@ struct Arch : BaseArch bool isBelLocationValid(BelId bel) const override; + BelId bel_by_block_idx(int x, int y, IdString type, int block_index) const; + // ------------------------------------------------- WireId getWireByName(IdStringList name) const override; @@ -403,7 +405,8 @@ struct Arch : BaseArch // ------------------------------------------------- - bool is_io_cell(IdString cell_type) const; // io.cc + bool is_io_cell(IdString cell_type) const; // io.cc + BelId get_io_pin_bel(const CycloneV::pin_info_t *pin) const; // io.cc // ------------------------------------------------- diff --git a/mistral/io.cc b/mistral/io.cc index f2517e5d..00918317 100644 --- a/mistral/io.cc +++ b/mistral/io.cc @@ -34,6 +34,7 @@ void Arch::create_gpio(int x, int y) add_bel_pin(bel, id_I, PORT_IN, get_port(CycloneV::GPIO, x, y, z, CycloneV::DATAIN, 0)); add_bel_pin(bel, id_OE, PORT_IN, get_port(CycloneV::GPIO, x, y, z, CycloneV::OEIN, 0)); add_bel_pin(bel, id_O, PORT_OUT, get_port(CycloneV::GPIO, x, y, z, CycloneV::DATAOUT, 0)); + bel_data(bel).block_index = z; } } @@ -50,4 +51,11 @@ bool Arch::is_io_cell(IdString cell_type) const } } +BelId Arch::get_io_pin_bel(const CycloneV::pin_info_t *pin) const +{ + auto pad = pin->pad; + CycloneV::pos_t pos = (pad & 0x3FFF); + return bel_by_block_idx(CycloneV::pos2x(pos), CycloneV::pos2y(pos), id_MISTRAL_IO, (pad >> 14)); +} + NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/mistral/pack.cc b/mistral/pack.cc index be0128fe..2c0ec80f 100644 --- a/mistral/pack.cc +++ b/mistral/pack.cc @@ -171,22 +171,126 @@ struct MistralPacker trim_design(); } + void prepare_io() + { + // Find the actual IO buffer corresponding to a port; and copy attributes across to it + // Note that this relies on Yosys to do IO buffer inference, to avoid tristate issues once we get to synthesised + // JSON. In all cases the nextpnr-inserted IO buffers are removed as redundant. + for (auto &port : sorted_ref(ctx->ports)) { + if (!ctx->cells.count(port.first)) + log_error("Port '%s' doesn't seem to have a corresponding top level IO\n", ctx->nameOf(port.first)); + CellInfo *ci = ctx->cells.at(port.first).get(); + + PortRef top_port; + top_port.cell = nullptr; + bool is_npnr_iob = false; + + if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) { + // Might have an input buffer (IB etc) connected to it + is_npnr_iob = true; + NetInfo *o = get_net_or_empty(ci, id_O); + if (o == nullptr) + ; + else if (o->users.size() > 1) + log_error("Top level pin '%s' has multiple input buffers\n", ctx->nameOf(port.first)); + else if (o->users.size() == 1) + top_port = o->users.at(0); + } + if (ci->type == ctx->id("$nextpnr_obuf") || ci->type == ctx->id("$nextpnr_iobuf")) { + // Might have an output buffer (OB etc) connected to it + is_npnr_iob = true; + NetInfo *i = get_net_or_empty(ci, id_I); + if (i != nullptr && i->driver.cell != nullptr) { + if (top_port.cell != nullptr) + log_error("Top level pin '%s' has multiple input/output buffers\n", ctx->nameOf(port.first)); + top_port = i->driver; + } + // Edge case of a bidirectional buffer driving an output pin + if (i->users.size() > 2) { + log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first)); + } else if (i->users.size() == 2) { + if (top_port.cell != nullptr) + log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first)); + for (auto &usr : i->users) { + if (usr.cell->type == ctx->id("$nextpnr_obuf") || usr.cell->type == ctx->id("$nextpnr_iobuf")) + continue; + top_port = usr; + break; + } + } + } + if (!is_npnr_iob) + log_error("Port '%s' doesn't seem to have a corresponding top level IO (internal cell type mismatch)\n", + ctx->nameOf(port.first)); + + if (top_port.cell == nullptr) { + log_info("Trimming port '%s' as it is unused.\n", ctx->nameOf(port.first)); + } else { + // Copy attributes to real IO buffer + if (ctx->io_attr.count(port.first)) { + for (auto &kv : ctx->io_attr.at(port.first)) { + top_port.cell->attrs[kv.first] = kv.second; + } + } + // Make sure that top level net is set correctly + port.second.net = top_port.cell->ports.at(top_port.port).net; + } + // Now remove the nextpnr-inserted buffer + disconnect_port(ctx, ci, id_I); + disconnect_port(ctx, ci, id_O); + ctx->cells.erase(port.first); + } + } + + void pack_io() + { + // Step 0: deal with top level inserted IO buffers + prepare_io(); + // Stage 1: apply constraints + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + // Iterate through all IO buffer primitives + if (!ctx->is_io_cell(ci->type)) + continue; + // We need all IO constrained at the moment, unconstrained IO are rare enough not to care + if (!ci->attrs.count(id_LOC)) + log_error("Found unconstrained IO '%s', these are currently unsupported\n", ctx->nameOf(ci)); + // Convert package pin constraint to bel constraint + std::string loc = ci->attrs.at(id_LOC).as_string(); + if (loc.compare(0, 4, "PIN_") != 0) + log_error("Expecting PIN_-prefixed pin for IO '%s', got '%s'\n", ctx->nameOf(ci), loc.c_str()); + auto pin_info = ctx->cyclonev->pin_find_name(loc.substr(4)); + if (pin_info == nullptr) + log_error("IO '%s' is constrained to invalid pin '%s'\n", ctx->nameOf(ci), loc.c_str()); + BelId bel = ctx->get_io_pin_bel(pin_info); + + if (bel == BelId()) { + log_error("IO '%s' is constrained to pin %s which is not a supported IO pin.\n", ctx->nameOf(ci), + loc.c_str()); + } else { + log_info("Constraining IO '%s' to pin %s (bel %s)\n", ctx->nameOf(ci), loc.c_str(), + ctx->nameOfBel(bel)); + ctx->bindBel(bel, ci, STRENGTH_LOCKED); + } + } + } + void run() { init_constant_nets(); pack_constants(); + pack_io(); } }; }; // namespace bool Arch::pack() { - // TODO: - // - Constrain IO - MistralPacker packer(getCtx()); packer.run(); + assignArchInfo(); + return true; } diff --git a/mistral/qsf.cc b/mistral/qsf.cc index 3f201aa1..0c95bf2e 100644 --- a/mistral/qsf.cc +++ b/mistral/qsf.cc @@ -51,7 +51,7 @@ void set_location_assignment_cmd(Context *ctx, const option_map_t &options, cons void set_instance_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector &pos_args) { - ctx->io_attr[ctx->id(options.at("to").at(0))][id_LOC] = pos_args.at(0); + ctx->io_attr[ctx->id(options.at("to").at(0))][ctx->id(options.at("name").at(0))] = pos_args.at(0); } void set_global_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector &pos_args) -- cgit v1.2.3 From 2612853238af4c84fa38c1c5e349fa19c5c51ebe Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 9 May 2021 14:28:56 +0100 Subject: mistral: Adding a function for computing ALM LUT masks Signed-off-by: gatecat --- mistral/arch.h | 2 ++ mistral/lab.cc | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/mistral/arch.h b/mistral/arch.h index 790c6782..868d8650 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -403,6 +403,8 @@ struct Arch : BaseArch void assign_control_sets(uint32_t lab); // lab.cc void reassign_alm_inputs(uint32_t lab, uint8_t alm); // lab.cc + uint64_t compute_lut_mask(uint32_t lab, uint8_t alm); // lab.cc + // ------------------------------------------------- bool is_io_cell(IdString cell_type) const; // io.cc diff --git a/mistral/lab.cc b/mistral/lab.cc index eab4d54d..da50fe33 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -653,4 +653,92 @@ const std::unordered_map Arch::comb_pinmap = { {id_E, id_B}, {id_F, id_A}, {id_Q, id_COMBOUT}, {id_SO, id_COMBOUT}, }; +namespace { +// gets the value of the ith LUT init property of a given cell +uint64_t get_lut_init(const CellInfo *cell, int i) +{ + if (cell->type == id_MISTRAL_NOT) { + return 1; + } else if (cell->type == id_MISTRAL_BUF) { + return 2; + } else { + IdString prop; + if (cell->type == id_MISTRAL_ALUT_ARITH) + prop = (i == 1) ? id_LUT1 : id_LUT0; + else + prop = id_LUT; + auto fnd = cell->params.find(prop); + if (fnd == cell->params.end()) + return 0; + else + return fnd->second.as_int64(); + } +} +// gets the state of a physical pin when evaluating the a given bit of LUT init for +bool get_phys_pin_val(bool l6_mode, bool arith_mode, int bit, IdString pin) +{ + switch (pin.index) { + case ID_A: + return (bit >> 0) & 0x1; + case ID_B: + return (bit >> 1) & 0x1; + case ID_C: + return (l6_mode && bit >= 32) ? ((bit >> 3) & 0x1) : ((bit >> 2) & 0x1); + case ID_D: + return (l6_mode && bit < 32) ? ((bit >> 3) & 0x1) : ((bit >> 2) & 0x1); + case ID_E0: + case ID_E1: + return l6_mode ? ((bit >> 5) & 0x1) : ((bit >> 3) & 0x1); + case ID_F0: + case ID_F1: + return arith_mode ? ((bit >> 3) & 0x1) : ((bit >> 4) & 0x1); + default: + NPNR_ASSERT_FALSE("unknown physical pin!"); + } +} +} // namespace + +uint64_t Arch::compute_lut_mask(uint32_t lab, uint8_t alm) +{ + uint64_t mask = 0; + auto &alm_data = labs.at(lab).alms.at(alm); + std::array luts{getBoundBelCell(alm_data.lut_bels[0]), getBoundBelCell(alm_data.lut_bels[1])}; + + for (int i = 0; i < 2; i++) { + CellInfo *lut = luts[i]; + if (!lut) + continue; + int offset = ((i == 1) && !alm_data.l6_mode) ? 32 : 0; + bool arith = lut->combInfo.is_carry; + for (int j = 0; j < (alm_data.l6_mode ? 64 : 32); j++) { + // Evaluate LUT function at this point + uint64_t init = get_lut_init(lut, (arith && j >= 16) ? 1 : 0); + int index = 0; + for (int k = 0; k < lut->combInfo.lut_input_count; k++) { + IdString log_pin = get_lut_pin(lut, k); + CellPinState state = lut->get_pin_state(log_pin); + if (state == PIN_0) + continue; + else if (state == PIN_1) + index |= (1 << k); + // Ignore if no associated physical pin + if (get_net_or_empty(lut, log_pin) == nullptr || lut->pin_data.at(log_pin).bel_pins.empty()) + continue; + // ALM inputs appear to be inverted by default (TODO: check!) + // so only invert if an inverter has _not_ been folded into the pin + bool inverted = (state != PIN_INV); + // Depermute physical pin + IdString phys_pin = lut->pin_data.at(log_pin).bel_pins.at(0); + if (get_phys_pin_val(alm_data.l6_mode, arith, j, phys_pin) != inverted) + index |= (1 << k); + } + if ((init >> index) & 0x1) { + mask |= (1U << uint64_t(j + offset)); + } + } + } + + return mask; +} + NEXTPNR_NAMESPACE_END \ No newline at end of file -- cgit v1.2.3 From c5d983066df541ad93a13904e96e0298489e2fcd Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 9 May 2021 16:27:57 +0100 Subject: mistral: Some preps for generating bitstreams Signed-off-by: gatecat --- mistral/arch.cc | 6 ++++ mistral/arch.h | 12 +++++++ mistral/base_bitstream.cc | 86 +++++++++++++++++++++++++++++++++++++++++++++++ mistral/lab.cc | 55 +++++++++++++++--------------- 4 files changed, 131 insertions(+), 28 deletions(-) create mode 100644 mistral/base_bitstream.cc diff --git a/mistral/arch.cc b/mistral/arch.cc index 592eddff..632fb0b2 100644 --- a/mistral/arch.cc +++ b/mistral/arch.cc @@ -303,6 +303,12 @@ WireId Arch::add_wire(int x, int y, IdString name, uint64_t flags) } } +bool Arch::wires_connected(WireId src, WireId dst) const +{ + PipId pip(src.node, dst.node); + return getBoundPipNet(pip) != nullptr; +} + PipId Arch::add_pip(WireId src, WireId dst) { wires[src].wires_downhill.push_back(dst); diff --git a/mistral/arch.h b/mistral/arch.h index 868d8650..860b3327 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -40,6 +40,10 @@ struct ArchArgs // These structures are used for fast ALM validity checking struct ALMInfo { + // Wires, so bitstream gen can determine connectivity + std::array comb_out; + std::array sel_clk, sel_ena, sel_aclr, sel_ef; + std::array ff_in, ff_out; // Pointers to bels std::array lut_bels; std::array ff_bels; @@ -326,6 +330,8 @@ struct Arch : BaseArch const std::vector &getWireBelPins(WireId wire) const override { return wires.at(wire).bel_pins; } AllWireRange getWires() const override { return AllWireRange(wires); } + bool wires_connected(WireId src, WireId dst) const; + // ------------------------------------------------- PipId getPipByName(IdStringList name) const override; @@ -461,9 +467,15 @@ struct Arch : BaseArch static const std::unordered_map cell_pins_db; // pins.cc CellPinStyle get_cell_pin_style(const CellInfo *cell, IdString port) const; // pins.cc + // ------------------------------------------------- + // List of IO constraints, used by QSF parser std::unordered_map> io_attr; void read_qsf(std::istream &in); // qsf.cc + + // ------------------------------------------------- + + void init_base_bitstream(); // base_bitstream.cc }; NEXTPNR_NAMESPACE_END diff --git a/mistral/base_bitstream.cc b/mistral/base_bitstream.cc new file mode 100644 index 00000000..95199b18 --- /dev/null +++ b/mistral/base_bitstream.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. + * + */ + +#include "log.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +// Device-specific default config for the sx120f die +void default_sx120f(CycloneV *cv) +{ + // Default PMA config? + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 11), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::TRISTATE); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 11), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::TRISTATE); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 23), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::DOWN); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 23), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::UP); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::UP); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::UP); + cv->bmux_b_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FPLL_DRV_EN, -1, false); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::HCLK_TOP_OUT_DRIVER, -1, CycloneV::TRISTATE); + // Default PLL config + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN0, -1, true); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN0_PRECOMP, -1, true); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN1, -1, true); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN1_PRECOMP, -1, true); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_BG_KICKSTART, -1, true); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_VBGMON_POWERDOWN, -1, true); + + // Discover these mux values using + // grep 'i [_A-Z0-9.]* 1' empty.bt + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 12), 69), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 13), 4), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 34), 69), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 35), 4), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 37), 31), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 40), 43), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 46), 69), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 47), 53), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 53), 69), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 54), 4), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 73), 68), true); + + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 18), 66), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 20), 8), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 27), 69), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 28), 43), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 59), 66), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 61), 8), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 68), 69), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(9, 69), 43), true); + + for (int z = 10; z <= 45; z++) + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(51, 80), z), true); +} +} // namespace + +void Arch::init_base_bitstream() +{ + switch (cyclonev->current_model()->variant.die.type) { + case CycloneV::SX120F: + default_sx120f(cyclonev); + break; + default: + log_error("FIXME: die type %s currently unsupported for bitgen.\n", + cyclonev->current_model()->variant.die.name); + } +} + +NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/mistral/lab.cc b/mistral/lab.cc index da50fe33..663b86e2 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -32,6 +32,7 @@ namespace { static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) { auto &lab = arch->labs.at(lab_idx); + auto &alm = lab.alms.at(z); // Create the combinational part of ALMs. // There are two of these, for the two LUT outputs, and these also contain the carry chain and associated logic // Each one has all 8 ALM inputs as input pins. In many cases only a subset of these are used; depending on mode; @@ -79,33 +80,32 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) 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); + alm.comb_out[i] = arch->add_wire(x, y, arch->id(stringf("COMBOUT[%d]", z * 2 + i))); + arch->add_bel_pin(bel, id_COMBOUT, PORT_OUT, alm.comb_out[i]); // Assign indexing - lab.alms.at(z).lut_bels.at(i) = bel; + alm.lut_bels.at(i) = bel; auto &b = arch->bel_data(bel); b.lab_data.lab = lab_idx; b.lab_data.alm = z; b.lab_data.idx = i; } // Create the control set and E/F selection - which is per pair of FF - 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))); + alm.sel_clk[i] = arch->add_wire(x, y, arch->id(stringf("CLK%c[%d]", i ? 'B' : 'T', z))); + alm.sel_ena[i] = arch->add_wire(x, y, arch->id(stringf("ENA%c[%d]", i ? 'B' : 'T', z))); + alm.sel_aclr[i] = arch->add_wire(x, y, arch->id(stringf("ACLR%c[%d]", i ? 'B' : 'T', z))); + alm.sel_ef[i] = arch->add_wire(x, y, arch->id(stringf("%cEF[%d]", i ? 'B' : 'T', z))); // Muxes - three CLK/ENA per LAB, two ACLR for (int j = 0; j < 3; j++) { - arch->add_pip(lab.clk_wires[j], sel_clk[i]); - arch->add_pip(lab.ena_wires[j], sel_ena[i]); + arch->add_pip(lab.clk_wires[j], alm.sel_clk[i]); + arch->add_pip(lab.ena_wires[j], alm.sel_ena[i]); if (j < 2) - arch->add_pip(lab.aclr_wires[j], sel_aclr[i]); + arch->add_pip(lab.aclr_wires[j], alm.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]); + arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::E1 : CycloneV::E0), alm.sel_ef[i]); + arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::F1 : CycloneV::F0), alm.sel_ef[i]); } // Create the flipflops and associated routing @@ -114,32 +114,31 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) 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); + alm.ff_in[i] = arch->add_wire(x, y, arch->id(stringf("FFIN[%d]", (z * 4) + i))); + arch->add_pip(alm.comb_out[i / 2], alm.ff_in[i]); + arch->add_pip(alm.sel_ef[i / 2], alm.ff_in[i]); // FF bel BelId bel = arch->add_bel(x, y, arch->id(stringf("ALM%d_FF%d", z, i)), id_MISTRAL_FF); - arch->add_bel_pin(bel, id_CLK, PORT_IN, 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_CLK, PORT_IN, alm.sel_clk[i / 2]); + arch->add_bel_pin(bel, id_ENA, PORT_IN, alm.sel_ena[i / 2]); + arch->add_bel_pin(bel, id_ACLR, PORT_IN, alm.sel_aclr[i / 2]); arch->add_bel_pin(bel, id_SCLR, PORT_IN, lab.sclr_wire); arch->add_bel_pin(bel, id_SLOAD, PORT_IN, lab.sload_wire); - arch->add_bel_pin(bel, id_DATAIN, PORT_IN, ff_in); - arch->add_bel_pin(bel, id_SDATA, PORT_IN, sel_ef[i / 2]); + arch->add_bel_pin(bel, id_DATAIN, PORT_IN, alm.ff_in[i]); + arch->add_bel_pin(bel, id_SDATA, PORT_IN, alm.sel_ef[i / 2]); // FF output - 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); + alm.ff_out[i] = arch->add_wire(x, y, arch->id(stringf("FFOUT[%d]", (z * 4) + i))); + arch->add_bel_pin(bel, id_Q, PORT_OUT, alm.ff_out[i]); // Output mux (*DFF*) WireId out = arch->get_port(CycloneV::LAB, x, y, z, outputs[i]); - arch->add_pip(ff_out, out); - arch->add_pip(comb_out, out); + arch->add_pip(alm.ff_out[i], out); + arch->add_pip(alm.comb_out[i / 2], out); // 'L' output mux where applicable if (i == 1 || i == 3) { WireId l_out = arch->get_port(CycloneV::LAB, x, y, z, l_outputs[i / 2]); - arch->add_pip(ff_out, l_out); - arch->add_pip(comb_out, l_out); + arch->add_pip(alm.ff_out[i], l_out); + arch->add_pip(alm.comb_out[i / 2], l_out); } lab.alms.at(z).ff_bels.at(i) = bel; -- cgit v1.2.3 From 386b5b901c9e62d527e84bbd0833f5908f778413 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 9 May 2021 16:51:28 +0100 Subject: mistral: Implement some misc. things Signed-off-by: gatecat --- mistral/arch.cc | 26 ++++++++++++++++++++++++-- mistral/arch.h | 21 ++++++++++++++------- mistral/constids.inc | 4 +++- mistral/globals.cc | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 mistral/globals.cc diff --git a/mistral/arch.cc b/mistral/arch.cc index 632fb0b2..5fc2a0b4 100644 --- a/mistral/arch.cc +++ b/mistral/arch.cc @@ -80,9 +80,11 @@ Arch::Arch(ArchArgs args) } } - for (auto gpio_pos : cyclonev->gpio_get_pos()) { + for (auto gpio_pos : cyclonev->gpio_get_pos()) create_gpio(CycloneV::pos2x(gpio_pos), CycloneV::pos2y(gpio_pos)); - } + + for (auto cmuxh_pos : cyclonev->cmuxh_get_pos()) + create_clkbuf(CycloneV::pos2x(cmuxh_pos), CycloneV::pos2y(cmuxh_pos)); // This import takes about 5s, perhaps long term we can speed it up, e.g. defer to Mistral more... log_info("Initialising routing graph...\n"); @@ -354,6 +356,26 @@ void Arch::assignArchInfo() } } +delay_t Arch::estimateDelay(WireId src, WireId dst) const +{ + int x0 = CycloneV::rn2x(src.node); + int y0 = CycloneV::rn2y(src.node); + int x1 = CycloneV::rn2x(dst.node); + int y1 = CycloneV::rn2y(dst.node); + return 100 * std::abs(y1 - y0) + 100 * std::abs(x1 - x0) + 100; +} + +delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +{ + if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId()) + return 100; + if (sink.cell->bel == BelId()) + return 100; + Loc src_loc = getBelLocation(net_info->driver.cell->bel); + Loc dst_loc = getBelLocation(sink.cell->bel); + return std::abs(dst_loc.y - src_loc.y) * 100 + std::abs(dst_loc.x - src_loc.x) * 100 + 100; +} + bool Arch::place() { std::string placer = str_or_default(settings, id("placer"), defaultPlacer); diff --git a/mistral/arch.h b/mistral/arch.h index 860b3327..24b45938 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -258,8 +258,14 @@ enum CellPinStyle PINSTYLE_COMB = 0x017, // combinational signal, defaults low, can be inverted and tied PINSTYLE_CLK = 0x107, // CLK type signal, invertible and defaults to disconnected - PINSTYLE_CE = 0x027, // CE type signal, invertible and defaults to enabled - PINSTYLE_RST = 0x017, // RST type signal, invertible and defaults to not reset + + // Technically speaking CE and RSTs should be invertible, too. But we don't use this currently due to the possible + // need to route one CE to two different LAB wires if both inverted and non-inverted variants are used in the same + // LAB This should be acheiveable by prerouting the LAB wiring inside assign_control_sets, but let's pass on this + // for a first attempt. + + PINSTYLE_CE = 0x023, // CE type signal, ~~invertible~~ and defaults to enabled + PINSTYLE_RST = 0x013, // RST type signal, ~~invertible~~ and defaults to not reset PINSTYLE_DEDI = 0x000, // dedicated signals, leave alone PINSTYLE_INP = 0x001, // general inputs, no inversion/tieing but defaults low PINSTYLE_PU = 0x022, // signals that float high and default high @@ -340,7 +346,7 @@ struct Arch : BaseArch 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); } + DelayQuad getPipDelay(PipId pip) const override { return DelayQuad(100); } UpDownhillPipRange getPipsDownhill(WireId wire) const override { return UpDownhillPipRange(wires.at(wire).wires_downhill, wire, false); @@ -352,8 +358,8 @@ struct Arch : BaseArch // ------------------------------------------------- - 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 estimateDelay(WireId src, WireId dst) const override; + delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const override; delay_t getDelayEpsilon() const override { return 10; }; delay_t getRipupDelayPenalty() const override { return 100; }; float getDelayNS(delay_t v) const override { return float(v) / 1000.0f; }; @@ -393,8 +399,9 @@ struct Arch : BaseArch return WireId(cyclonev->pnode_to_rnode(CycloneV::pnode(bt, x, y, port, bi, pi))); } - void create_lab(int x, int y); // lab.cc - void create_gpio(int x, int y); // io.cc + void create_lab(int x, int y); // lab.cc + void create_gpio(int x, int y); // io.cc + void create_clkbuf(int x, int y); // globals.cc // ------------------------------------------------- diff --git a/mistral/constids.inc b/mistral/constids.inc index 342ca353..2477f693 100644 --- a/mistral/constids.inc +++ b/mistral/constids.inc @@ -73,4 +73,6 @@ X(WIRE) X(GND) X(VCC) -X(LOC) \ No newline at end of file +X(LOC) + +X(MISTRAL_CLKBUF) \ No newline at end of file diff --git a/mistral/globals.cc b/mistral/globals.cc new file mode 100644 index 00000000..34e569d3 --- /dev/null +++ b/mistral/globals.cc @@ -0,0 +1,37 @@ +/* + * 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_clkbuf(int x, int y) +{ + for (int z = 0; z < 4; z++) { + // For now we only consider the input path from general routing, other inputs like dedicated clock pins are + // still a TODO + BelId bel = add_bel(x, y, id(stringf("CLKBUF[%d]", z)), id_MISTRAL_CLKBUF); + add_bel_pin(bel, id_A, PORT_IN, get_port(CycloneV::CMUXHG, x, y, -1, CycloneV::CLKIN, z)); + add_bel_pin(bel, id_Q, PORT_OUT, get_port(CycloneV::CMUXHG, x, y, z, CycloneV::CLKOUT)); + } +} + +NEXTPNR_NAMESPACE_END -- cgit v1.2.3 From a5815265288aa373c52ca1786f2303fb6eb83162 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 9 May 2021 17:11:41 +0100 Subject: mistral: Python and GUI stub Signed-off-by: gatecat --- gui/mistral/family.cmake | 1 + gui/mistral/mainwindow.cc | 86 +++++++++++++++++++++++++++++++ gui/mistral/mainwindow.h | 49 ++++++++++++++++++ gui/mistral/nextpnr.qrc | 2 + mistral/arch_pybindings.cc | 87 +++++++++++++++++++++++++++++++ mistral/arch_pybindings.h | 125 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 350 insertions(+) create mode 100644 gui/mistral/family.cmake create mode 100644 gui/mistral/mainwindow.cc create mode 100644 gui/mistral/mainwindow.h create mode 100644 gui/mistral/nextpnr.qrc create mode 100644 mistral/arch_pybindings.cc create mode 100644 mistral/arch_pybindings.h diff --git a/gui/mistral/family.cmake b/gui/mistral/family.cmake new file mode 100644 index 00000000..de737704 --- /dev/null +++ b/gui/mistral/family.cmake @@ -0,0 +1 @@ +target_include_directories(gui_mistral PRIVATE ${MISTRAL_ROOT}/lib) diff --git a/gui/mistral/mainwindow.cc b/gui/mistral/mainwindow.cc new file mode 100644 index 00000000..58b785e6 --- /dev/null +++ b/gui/mistral/mainwindow.cc @@ -0,0 +1,86 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * 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 "mainwindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "design_utils.h" +#include "log.h" + +static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } + +NEXTPNR_NAMESPACE_BEGIN + +MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) + : BaseMainWindow(std::move(context), handler, parent) +{ + initMainResource(); + + std::string title = "nextpnr-mistral - [EMPTY]"; + setWindowTitle(title.c_str()); + + connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext); + + createMenu(); +} + +MainWindow::~MainWindow() {} + +void MainWindow::createMenu() +{ + // Add arch specific actions + + // Add actions in menus +} + +void MainWindow::new_proj() +{ + QStringList arch; + + // TODO: better device picker + arch.push_back("5CSEBA6U23I7"); + + bool ok; + QString item = QInputDialog::getItem(this, "Select new context", "Chip:", arch, 0, false, &ok); + if (ok && !item.isEmpty()) { + ArchArgs chipArgs; + chipArgs.device = item.toStdString(); + ctx = std::unique_ptr(new Context(chipArgs)); + actionLoadJSON->setEnabled(true); + Q_EMIT contextChanged(ctx.get()); + } +} + +void MainWindow::newContext(Context *ctx) +{ + std::string title = "nextpnr-nexus - " + ctx->getChipName(); + setWindowTitle(title.c_str()); +} + +void MainWindow::onDisableActions() {} + +void MainWindow::onUpdateActions() {} + +NEXTPNR_NAMESPACE_END diff --git a/gui/mistral/mainwindow.h b/gui/mistral/mainwindow.h new file mode 100644 index 00000000..13556fa1 --- /dev/null +++ b/gui/mistral/mainwindow.h @@ -0,0 +1,49 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * 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 MAINWINDOW_H +#define MAINWINDOW_H + +#include "../basewindow.h" + +NEXTPNR_NAMESPACE_BEGIN + +class MainWindow : public BaseMainWindow +{ + Q_OBJECT + + public: + explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); + virtual ~MainWindow(); + + public: + void createMenu(); + + void onDisableActions() override; + void onUpdateActions() override; + + protected Q_SLOTS: + void new_proj() override; + + void newContext(Context *ctx); +}; + +NEXTPNR_NAMESPACE_END + +#endif // MAINWINDOW_H diff --git a/gui/mistral/nextpnr.qrc b/gui/mistral/nextpnr.qrc new file mode 100644 index 00000000..03585ec0 --- /dev/null +++ b/gui/mistral/nextpnr.qrc @@ -0,0 +1,2 @@ + + diff --git a/mistral/arch_pybindings.cc b/mistral/arch_pybindings.cc new file mode 100644 index 00000000..d93b7e28 --- /dev/null +++ b/mistral/arch_pybindings.cc @@ -0,0 +1,87 @@ + +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xen + * Copyright (C) 2018 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. + * + */ + +#ifndef NO_PYTHON + +#include "arch_pybindings.h" +#include "nextpnr.h" +#include "pybindings.h" + +NEXTPNR_NAMESPACE_BEGIN + +void arch_wrap_python(py::module &m) +{ + using namespace PythonConversion; + py::class_(m, "ArchArgs").def_readwrite("device", &ArchArgs::device); + + py::class_(m, "BelId").def_readwrite("pos", &BelId::pos).def_readwrite("z", &BelId::z); + + py::class_(m, "WireId").def_readwrite("node", &WireId::node); + + py::class_(m, "PipId").def_readwrite("src", &PipId::src).def_readwrite("dst", &PipId::dst); + + auto arch_cls = py::class_(m, "Arch").def(py::init()); + auto ctx_cls = py::class_(m, "Context") + .def("checksum", &Context::checksum) + .def("pack", &Context::pack) + .def("place", &Context::place) + .def("route", &Context::route); + + fn_wrapper_2a, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "wires_connected"); + fn_wrapper_2a, + pass_through, pass_through>::def_wrap(ctx_cls, "compute_lut_mask"); + + typedef std::unordered_map> CellMap; + typedef std::unordered_map> NetMap; + typedef std::unordered_map AliasMap; + typedef std::unordered_map HierarchyMap; + + auto belpin_cls = py::class_>(m, "BelPin"); + readonly_wrapper>::def_wrap(belpin_cls, "bel"); + readonly_wrapper>::def_wrap(belpin_cls, "pin"); + + typedef const std::vector &BelRange; + + typedef const UpDownhillPipRange UphillPipRange; + typedef const UpDownhillPipRange DownhillPipRange; + + typedef AllWireRange WireRange; + typedef const std::vector &BelPinRange; + + typedef const std::vector &BelBucketRange; + typedef const std::vector &BelRangeForBelBucket; +#include "arch_pybindings_shared.h" + + WRAP_RANGE(m, Bel, conv_to_str); + WRAP_RANGE(m, AllWire, conv_to_str); + WRAP_RANGE(m, AllPip, conv_to_str); + WRAP_RANGE(m, UpDownhillPip, conv_to_str); + WRAP_RANGE(m, BelPin, wrap_context); + + WRAP_MAP_UPTR(m, CellMap, "IdCellMap"); + WRAP_MAP_UPTR(m, NetMap, "IdNetMap"); + WRAP_MAP(m, HierarchyMap, wrap_context, "HierarchyMap"); +} + +NEXTPNR_NAMESPACE_END + +#endif // NO_PYTHON diff --git a/mistral/arch_pybindings.h b/mistral/arch_pybindings.h new file mode 100644 index 00000000..aa5b322e --- /dev/null +++ b/mistral/arch_pybindings.h @@ -0,0 +1,125 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xen + * Copyright (C) 2018 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. + * + */ +#ifndef ARCH_PYBINDINGS_H +#define ARCH_PYBINDINGS_H +#ifndef NO_PYTHON + +#include "nextpnr.h" +#include "pybindings.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace PythonConversion { + +template <> struct string_converter +{ + BelId from_str(Context *ctx, std::string name) { return ctx->getBelByNameStr(name); } + + std::string to_str(Context *ctx, BelId id) + { + if (id == BelId()) + throw bad_wrap(); + return ctx->getBelName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + const BelId &from_str(Context *ctx, std::string name) { NPNR_ASSERT_FALSE("unreachable"); } + + std::string to_str(Context *ctx, const BelId &id) + { + if (id == BelId()) + throw bad_wrap(); + return ctx->getBelName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByNameStr(name); } + + std::string to_str(Context *ctx, WireId id) + { + if (id == WireId()) + throw bad_wrap(); + return ctx->getWireName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByNameStr(name); } + + std::string to_str(Context *ctx, WireId id) + { + if (id == WireId()) + throw bad_wrap(); + return ctx->getWireName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + PipId from_str(Context *ctx, std::string name) { return ctx->getPipByNameStr(name); } + + std::string to_str(Context *ctx, PipId id) + { + if (id == PipId()) + throw bad_wrap(); + return ctx->getPipName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + BelPin from_str(Context *ctx, std::string name) + { + NPNR_ASSERT_FALSE("string_converter::from_str not implemented"); + } + + std::string to_str(Context *ctx, BelPin pin) + { + if (pin.bel == BelId()) + throw bad_wrap(); + return ctx->getBelName(pin.bel).str(ctx) + "/" + pin.pin.str(ctx); + } +}; + +template <> struct string_converter +{ + BelPin from_str(Context *ctx, std::string name) + { + NPNR_ASSERT_FALSE("string_converter::from_str not implemented"); + } + + std::string to_str(Context *ctx, const BelPin &pin) + { + if (pin.bel == BelId()) + throw bad_wrap(); + return ctx->getBelName(pin.bel).str(ctx) + "/" + pin.pin.str(ctx); + } +}; + +} // namespace PythonConversion + +NEXTPNR_NAMESPACE_END +#endif +#endif -- cgit v1.2.3 From ad5e5f80ca4ad798d1afbddcae84e04f3a26f0e5 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 9 May 2021 18:28:49 +0100 Subject: mistral: Rename clock buffer primitive Signed-off-by: gatecat --- mistral/constids.inc | 2 +- mistral/globals.cc | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mistral/constids.inc b/mistral/constids.inc index 2477f693..9450d8b1 100644 --- a/mistral/constids.inc +++ b/mistral/constids.inc @@ -75,4 +75,4 @@ X(VCC) X(LOC) -X(MISTRAL_CLKBUF) \ No newline at end of file +X(MISTRAL_CLKENA) \ No newline at end of file diff --git a/mistral/globals.cc b/mistral/globals.cc index 34e569d3..2d1b2ef8 100644 --- a/mistral/globals.cc +++ b/mistral/globals.cc @@ -28,9 +28,10 @@ void Arch::create_clkbuf(int x, int y) for (int z = 0; z < 4; z++) { // For now we only consider the input path from general routing, other inputs like dedicated clock pins are // still a TODO - BelId bel = add_bel(x, y, id(stringf("CLKBUF[%d]", z)), id_MISTRAL_CLKBUF); + BelId bel = add_bel(x, y, id(stringf("CLKBUF[%d]", z)), id_MISTRAL_CLKENA); add_bel_pin(bel, id_A, PORT_IN, get_port(CycloneV::CMUXHG, x, y, -1, CycloneV::CLKIN, z)); add_bel_pin(bel, id_Q, PORT_OUT, get_port(CycloneV::CMUXHG, x, y, z, CycloneV::CLKOUT)); + // TODO: enable pin } } -- cgit v1.2.3 From 27eb3be7dae5038099aab8299e0f1d9d675fd855 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sun, 9 May 2021 19:48:04 +0100 Subject: mistral: Add stub RBF generation Signed-off-by: gatecat --- mistral/arch.h | 1 + mistral/base_bitstream.cc | 18 +++++----- mistral/bitstream.cc | 84 +++++++++++++++++++++++++++++++++++++++++++++++ mistral/main.cc | 14 +++++++- 4 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 mistral/bitstream.cc diff --git a/mistral/arch.h b/mistral/arch.h index 24b45938..e22d26fd 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -483,6 +483,7 @@ struct Arch : BaseArch // ------------------------------------------------- void init_base_bitstream(); // base_bitstream.cc + void build_bitstream(); // bitstream.cc }; NEXTPNR_NAMESPACE_END diff --git a/mistral/base_bitstream.cc b/mistral/base_bitstream.cc index 95199b18..bf85c6b9 100644 --- a/mistral/base_bitstream.cc +++ b/mistral/base_bitstream.cc @@ -33,15 +33,17 @@ void default_sx120f(CycloneV *cv) cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 23), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::UP); cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FFPLL_IQCLK_DIRECTION, 0, CycloneV::UP); cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FFPLL_IQCLK_DIRECTION, 1, CycloneV::UP); - cv->bmux_b_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FPLL_DRV_EN, -1, false); - cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::HCLK_TOP_OUT_DRIVER, -1, CycloneV::TRISTATE); + cv->bmux_b_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::FPLL_DRV_EN, 0, 0); + cv->bmux_m_set(CycloneV::PMA3, CycloneV::xy2pos(0, 35), CycloneV::HCLK_TOP_OUT_DRIVER, 0, CycloneV::TRISTATE); // Default PLL config - cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN0, -1, true); - cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN0_PRECOMP, -1, true); - cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN1, -1, true); - cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN1_PRECOMP, -1, true); - cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_BG_KICKSTART, -1, true); - cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_VBGMON_POWERDOWN, -1, true); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN0, 0, 1); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN0_PRECOMP, 0, 1); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN1, 0, 1); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_ATB_EN1_PRECOMP, 0, 1); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_BG_KICKSTART, 0, 1); + cv->bmux_b_set(CycloneV::FPLL, CycloneV::xy2pos(0, 73), CycloneV::PL_AUX_VBGMON_POWERDOWN, 0, 1); + // Default TERM config + cv->bmux_b_set(CycloneV::TERM, CycloneV::xy2pos(89, 34), CycloneV::INTOSC_2_EN, 0, 0); // Discover these mux values using // grep 'i [_A-Z0-9.]* 1' empty.bt diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc new file mode 100644 index 00000000..9ed3a161 --- /dev/null +++ b/mistral/bitstream.cc @@ -0,0 +1,84 @@ +/* + * 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 +namespace { +struct MistralBitgen +{ + MistralBitgen(Context *ctx) : ctx(ctx), cv(ctx->cyclonev){}; + Context *ctx; + CycloneV *cv; + + void init() + { + ctx->init_base_bitstream(); + // Default options + cv->opt_b_set(CycloneV::ALLOW_DEVICE_WIDE_OUTPUT_ENABLE_DIS, true); + cv->opt_n_set(CycloneV::CRC_DIVIDE_ORDER, 8); + cv->opt_b_set(CycloneV::CVP_CONF_DONE_EN, true); + cv->opt_b_set(CycloneV::DEVICE_WIDE_RESET_EN, true); + cv->opt_n_set(CycloneV::DRIVE_STRENGTH, 8); + cv->opt_b_set(CycloneV::IOCSR_READY_FROM_CSR_DONE_EN, true); + cv->opt_b_set(CycloneV::NCEO_DIS, true); + cv->opt_b_set(CycloneV::OCT_DONE_DIS, true); + cv->opt_r_set(CycloneV::OPT_A, 0x1dff); + cv->opt_r_set(CycloneV::OPT_B, 0xffffff402dffffffULL); + cv->opt_b_set(CycloneV::RELEASE_CLEARS_BEFORE_TRISTATES_DIS, true); + cv->opt_b_set(CycloneV::RETRY_CONFIG_ON_ERROR_EN, true); + cv->opt_r_set(CycloneV::START_UP_CLOCK, 0x3F); + } + + void write_routing() + { + for (auto net : sorted(ctx->nets)) { + NetInfo *ni = net.second; + for (auto wire : sorted_ref(ni->wires)) { + PipId pip = wire.second.pip; + if (pip == PipId()) + continue; + WireId src = ctx->getPipSrcWire(pip), dst = ctx->getPipDstWire(pip); + // Only write out routes that are entirely in the Mistral domain. Everything else is dealt with + // specially + if (src.is_nextpnr_created() || dst.is_nextpnr_created()) + continue; + cv->rnode_link(src.node, dst.node); + } + } + } + + void run() + { + cv->clear(); + init(); + write_routing(); + } +}; +} // namespace + +void Arch::build_bitstream() +{ + MistralBitgen gen(getCtx()); + gen.run(); +} + +NEXTPNR_NAMESPACE_END diff --git a/mistral/main.cc b/mistral/main.cc index d5816693..9147a68b 100644 --- a/mistral/main.cc +++ b/mistral/main.cc @@ -50,12 +50,24 @@ po::options_description MistralCommandHandler::getArchOptions() specific.add_options()("mistral", po::value(), "path to mistral root"); specific.add_options()("device", po::value(), "device name (e.g. 5CSEBA6U23I7)"); specific.add_options()("qsf", po::value(), "path to QSF constraints file"); + specific.add_options()("rbf", po::value(), "RBF bitstream to write"); + return specific; } void MistralCommandHandler::customBitstream(Context *ctx) { - // TODO: rbf gen via mistral + if (vm.count("rbf")) { + std::string filename = vm["rbf"].as(); + ctx->build_bitstream(); + std::vector data; + ctx->cyclonev->rbf_save(data); + + std::ofstream out(filename, std::ios::binary); + if (!out) + log_error("Failed to open output RBF file %s.\n", filename.c_str()); + out.write(reinterpret_cast(data.data()), data.size()); + } } std::unique_ptr MistralCommandHandler::createContext(std::unordered_map &values) -- cgit v1.2.3 From dea4c6f53f23aa228ced3016903c62bdc3745e34 Mon Sep 17 00:00:00 2001 From: gatecat Date: Mon, 10 May 2021 21:23:47 +0100 Subject: mistral: Setting some more boilerplate bits Signed-off-by: gatecat --- mistral/base_bitstream.cc | 14 ++++++- mistral/bitstream.cc | 102 ++++++++++++++++++++++++++++++++++++++++++++++ mistral/family.cmake | 3 ++ 3 files changed, 118 insertions(+), 1 deletion(-) diff --git a/mistral/base_bitstream.cc b/mistral/base_bitstream.cc index bf85c6b9..759cdfeb 100644 --- a/mistral/base_bitstream.cc +++ b/mistral/base_bitstream.cc @@ -45,6 +45,18 @@ void default_sx120f(CycloneV *cv) // Default TERM config cv->bmux_b_set(CycloneV::TERM, CycloneV::xy2pos(89, 34), CycloneV::INTOSC_2_EN, 0, 0); + // TODO: what if these pins are used? where do these come from + for (int z = 0; z < 4; z++) { + cv->bmux_m_set(CycloneV::GPIO, CycloneV::xy2pos(89, 43), CycloneV::IOCSR_STD, z, CycloneV::NVR_LOW); + cv->bmux_m_set(CycloneV::GPIO, CycloneV::xy2pos(89, 66), CycloneV::IOCSR_STD, z, CycloneV::NVR_LOW); + } + for (int y : {38, 44, 51, 58, 65, 73, 79}) { + // TODO: Why only these upper DQS? is there a pattern? + cv->bmux_b_set(CycloneV::DQS16, CycloneV::xy2pos(89, y), CycloneV::RB_2X_CLK_DQS_INV, 0, 1); + cv->bmux_b_set(CycloneV::DQS16, CycloneV::xy2pos(89, y), CycloneV::RB_ACLR_LFIFO_EN, 0, 1); + cv->bmux_b_set(CycloneV::DQS16, CycloneV::xy2pos(89, y), CycloneV::RB_LFIFO_BYPASS, 0, 0); + } + // Discover these mux values using // grep 'i [_A-Z0-9.]* 1' empty.bt cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 12), 69), true); @@ -54,7 +66,7 @@ void default_sx120f(CycloneV *cv) cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 37), 31), true); cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 40), 43), true); cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 46), 69), true); - cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 47), 53), true); + cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 47), 43), true); cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 53), 69), true); cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 54), 4), true); cv->inv_set(CycloneV::rnode(CycloneV::GOUT, CycloneV::xy2pos(0, 73), 68), true); diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index 9ed3a161..2c612a73 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -46,6 +46,107 @@ struct MistralBitgen cv->opt_b_set(CycloneV::RELEASE_CLEARS_BEFORE_TRISTATES_DIS, true); cv->opt_b_set(CycloneV::RETRY_CONFIG_ON_ERROR_EN, true); cv->opt_r_set(CycloneV::START_UP_CLOCK, 0x3F); + // Default inversion + write_default_inv(); + } + + void write_default_inv() + { + // Some PNODEs are inverted by default. Set them up here. + for (const auto &pn2r : cv->get_all_p2r()) { + const auto &pn = pn2r.first; + auto pt = CycloneV::pn2pt(pn); + auto pi = CycloneV::pn2pi(pn); + + switch (CycloneV::pn2bt(pn)) { + case CycloneV::HMC: { + // HMC OE are inverted to set OE=0, i.e. unused pins floating + // TODO: handle the case when we are using the HMC or HMC bypass + std::string name(CycloneV::port_type_names[pt]); + if (name.compare(0, 5, "IOINT") != 0 || name.compare(name.size() - 2, 2, "OE") != 0) + continue; + cv->inv_set(pn2r.second, true); + break; + }; + // HPS IO - TODO: what about when we actually support the HPS primitives? + case CycloneV::HPS_BOOT: { + switch (pt) { + case CycloneV::CSEL_EN: + case CycloneV::BSEL_EN: + case CycloneV::BOOT_FROM_FPGA_READY: + case CycloneV::BOOT_FROM_FPGA_ON_FAILURE: + cv->inv_set(pn2r.second, true); + break; + case CycloneV::CSEL: + if (pi < 2) + cv->inv_set(pn2r.second, true); + break; + case CycloneV::BSEL: + if (pi < 3) + cv->inv_set(pn2r.second, true); + break; + default: + break; + }; + break; + }; + case CycloneV::HPS_CROSS_TRIGGER: { + if (pt == CycloneV::CLK_EN) + cv->inv_set(pn2r.second, true); + break; + }; + case CycloneV::HPS_TEST: { + if (pt == CycloneV::CFG_DFX_BYPASS_ENABLE) + cv->inv_set(pn2r.second, true); + break; + }; + case CycloneV::GPIO: { + // Ignore GPIO used by the design + BelId bel = ctx->bel_by_block_idx(CycloneV::pn2x(pn), CycloneV::pn2y(pn), id_MISTRAL_IO, + CycloneV::pn2bi(pn)); + if (bel != BelId() && ctx->getBoundBelCell(bel) != nullptr) + continue; + // Bonded IO invert OEIN.1 which disables the output buffer and floats the IO + // Unbonded IO invert OEIN.0 which enables the output buffer, and {DATAIN.[0123]} to drive a constant + // GND, presumably for power/EMI reasons + bool is_bonded = cv->pin_find_pnode(pn) != nullptr; + if (is_bonded && (pt != CycloneV::OEIN || pi != 1)) + continue; + if (!is_bonded && (pt != CycloneV::DATAIN) && (pt != CycloneV::OEIN || pi != 0)) + continue; + cv->inv_set(pn2r.second, true); + break; + }; + case CycloneV::FPLL: { + if (pt == CycloneV::EXTSWITCH || (pt == CycloneV::CLKEN && pi < 2)) + cv->inv_set(pn2r.second, true); + break; + }; + default: + break; + } + } + } + + void write_dqs() + { + for (auto pos : cv->dqs16_get_pos()) { + int x = CycloneV::pos2x(pos), y = CycloneV::pos2y(pos); + // DQS bypass for used output pins + for (int z = 0; z < 16; z++) { + int ioy = y + (z / 4) - 2; + if (ioy < 0 || ioy >= int(cv->get_tile_sy())) + continue; + BelId bel = ctx->bel_by_block_idx(x, ioy, id_MISTRAL_IO, z % 4); + if (bel == BelId()) + continue; + CellInfo *ci = ctx->getBoundBelCell(bel); + if (ci == nullptr || (ci->type != id_MISTRAL_IO && ci->type != id_MISTRAL_OB)) + continue; // not an output + cv->bmux_m_set(CycloneV::DQS16, pos, CycloneV::INPUT_REG4_SEL, z, CycloneV::SEL_LOCKED_DPA); + cv->bmux_r_set(CycloneV::DQS16, pos, CycloneV::RB_T9_SEL_EREG_CFF_DELAY, z, 0x1f); + } + } } void write_routing() @@ -71,6 +172,7 @@ struct MistralBitgen cv->clear(); init(); write_routing(); + write_dqs(); } }; } // namespace diff --git a/mistral/family.cmake b/mistral/family.cmake index 441d81db..79681ba8 100644 --- a/mistral/family.cmake +++ b/mistral/family.cmake @@ -2,10 +2,13 @@ set(MISTRAL_ROOT "" CACHE STRING "Mistral install path") aux_source_directory(${MISTRAL_ROOT}/lib MISTRAL_LIB_FILES) add_library(mistral STATIC ${MISTRAL_LIB_FILES}) +target_compile_options(mistral PRIVATE -Wno-maybe-uninitialized) find_package(LibLZMA REQUIRED) foreach(family_target ${family_targets}) target_include_directories(${family_target} PRIVATE ${MISTRAL_ROOT}/lib ${LIBLZMA_INCLUDE_DIRS}) target_link_libraries(${family_target} PRIVATE mistral ${LIBLZMA_LIBRARIES}) + # Currently required to avoid issues with mistral (LTO means the warnings can end up in nextpnr) + target_link_options(${family_target} PRIVATE -Wno-maybe-uninitialized) endforeach() -- cgit v1.2.3 From d1f635242d2984331da85212b478950bf299b636 Mon Sep 17 00:00:00 2001 From: gatecat Date: Mon, 10 May 2021 21:37:59 +0100 Subject: mistral: Add some IO configuration Signed-off-by: gatecat --- mistral/bitstream.cc | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index 2c612a73..2d64c337 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -167,12 +167,42 @@ struct MistralBitgen } } + void write_io_cell(CellInfo *ci, int x, int y, int bi) + { + bool is_output = + (ci->type == id_MISTRAL_OB || (ci->type == id_MISTRAL_IO && get_net_or_empty(ci, id_OE) != nullptr)); + auto pos = CycloneV::xy2pos(x, y); + // TODO: configurable pull, IO standard, etc + cv->bmux_b_set(CycloneV::GPIO, pos, CycloneV::USE_WEAK_PULLUP, bi, false); + if (is_output) { + cv->bmux_m_set(CycloneV::GPIO, pos, CycloneV::DRIVE_STRENGTH, bi, CycloneV::V3P3_LVTTL_16MA_LVCMOS_2MA); + cv->bmux_m_set(CycloneV::GPIO, pos, CycloneV::IOCSR_STD, bi, CycloneV::DIS); + } + // There seem to be two mirrored OEIN inversion bits for constant OE for inputs/outputs. This might be to + // prevent a single bitflip from turning inputs to outputs and messing up other devices on the boards, notably + // ECP5 does similar. OEIN.0 inverted for outputs; OEIN.1 for inputs + cv->inv_set(cv->pnode_to_rnode(CycloneV::pnode(CycloneV::GPIO, pos, CycloneV::OEIN, bi, is_output ? 0 : 1)), + true); + } + + void write_cells() + { + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + Loc loc = ctx->getBelLocation(ci->bel); + int bi = ctx->bel_data(ci->bel).block_index; + if (ctx->is_io_cell(ci->type)) + write_io_cell(ci, loc.x, loc.y, bi); + } + } + void run() { cv->clear(); init(); write_routing(); write_dqs(); + write_cells(); } }; } // namespace -- cgit v1.2.3 From bacba274a2edf4bf8cb05b109388e6e18ca5fc16 Mon Sep 17 00:00:00 2001 From: gatecat Date: Tue, 11 May 2021 21:07:22 +0100 Subject: mistral: Write LUT inits Signed-off-by: gatecat --- mistral/bitstream.cc | 37 +++++++++++++++++++++++++++++++++++++ mistral/lab.cc | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index 2d64c337..3337c38e 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -196,6 +196,42 @@ struct MistralBitgen } } + void write_alm(uint32_t lab, uint8_t alm) + { + auto &alm_data = ctx->labs.at(lab).alms.at(alm); + + std::array luts{ctx->getBoundBelCell(alm_data.lut_bels[0]), + ctx->getBoundBelCell(alm_data.lut_bels[1])}; + std::array ffs{ + ctx->getBoundBelCell(alm_data.ff_bels[0]), ctx->getBoundBelCell(alm_data.ff_bels[1]), + ctx->getBoundBelCell(alm_data.ff_bels[2]), ctx->getBoundBelCell(alm_data.ff_bels[3])}; + // Skip empty ALMs + if (std::all_of(luts.begin(), luts.end(), [](CellInfo *c) { return !c; }) && + std::all_of(ffs.begin(), ffs.end(), [](CellInfo *c) { return !c; })) + return; + + auto pos = alm_data.lut_bels[0].pos; + // Combinational mode - TODO: flop feedback + cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::MODE, alm, alm_data.l6_mode ? CycloneV::L6 : CycloneV::L5); + // LUT function + cv->bmux_r_set(CycloneV::LAB, pos, CycloneV::LUT_MASK, alm, ctx->compute_lut_mask(lab, alm)); + // DFF output - foce to LUT for now... + cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::TDFF0, alm, CycloneV::NLUT); + cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::TDFF1, alm, CycloneV::NLUT); + cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::TDFF1L, alm, CycloneV::NLUT); + cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::BDFF0, alm, CycloneV::NLUT); + cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::BDFF1, alm, CycloneV::NLUT); + cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::BDFF1L, alm, CycloneV::NLUT); + } + + void write_labs() + { + for (size_t lab = 0; lab < ctx->labs.size(); lab++) { + for (uint8_t alm = 0; alm < 10; alm++) + write_alm(lab, alm); + } + } + void run() { cv->clear(); @@ -203,6 +239,7 @@ struct MistralBitgen write_routing(); write_dqs(); write_cells(); + write_labs(); } }; } // namespace diff --git a/mistral/lab.cc b/mistral/lab.cc index 663b86e2..e93ef8a8 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -732,11 +732,45 @@ uint64_t Arch::compute_lut_mask(uint32_t lab, uint8_t alm) index |= (1 << k); } if ((init >> index) & 0x1) { - mask |= (1U << uint64_t(j + offset)); + mask |= (1ULL << uint64_t(j + offset)); } } } + // TODO: always inverted, or just certain paths? + mask = ~mask; + +#if 1 + if (getCtx()->debug) { + auto pos = alm_data.lut_bels[0].pos; + log("ALM %03d.%03d.%d\n", CycloneV::pos2x(pos), CycloneV::pos2y(pos), alm); + for (int i = 0; i < 2; i++) { + log(" LUT%d: ", i); + if (luts[i]) { + log("%s:%s", nameOf(luts[i]), nameOf(luts[i]->type)); + for (auto &pin : luts[i]->pin_data) { + if (!luts[i]->ports.count(pin.first) || luts[i]->ports.at(pin.first).type != PORT_IN) + continue; + log(" %s:", nameOf(pin.first)); + if (pin.second.state == PIN_0) + log("0"); + else if (pin.second.state == PIN_1) + log("1"); + else if (pin.second.state == PIN_INV) + log("~"); + for (auto bp : pin.second.bel_pins) + log("%s", nameOf(bp)); + } + } else { + log(""); + } + log("\n"); + } + log("INIT: %016lx\n", mask); + log("\n"); + } +#endif + return mask; } -- cgit v1.2.3 From 18e05ec85258bc37baff9028d775c43741725d20 Mon Sep 17 00:00:00 2001 From: gatecat Date: Wed, 12 May 2021 19:51:02 +0100 Subject: mistral: Fix constant trimming Signed-off-by: gatecat --- mistral/constids.inc | 1 + mistral/pack.cc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mistral/constids.inc b/mistral/constids.inc index 9450d8b1..f1b51ee5 100644 --- a/mistral/constids.inc +++ b/mistral/constids.inc @@ -72,6 +72,7 @@ X(WIRE) X(GND) X(VCC) +X(Y) X(LOC) diff --git a/mistral/pack.cc b/mistral/pack.cc index 2c0ec80f..78657321 100644 --- a/mistral/pack.cc +++ b/mistral/pack.cc @@ -137,7 +137,7 @@ struct MistralPacker CellInfo *ci = cell.second; if (ci->type != id_MISTRAL_NOT && ci->type != id_GND && ci->type != id_VCC) continue; - IdString port = (ci->type == id_MISTRAL_NOT) ? id_Q : ci->type; + IdString port = (ci->type == id_MISTRAL_NOT) ? id_Q : id_Y; NetInfo *out = get_net_or_empty(ci, port); if (out == nullptr) { trim_cells.push_back(ci->name); -- cgit v1.2.3 From 7574eab2b603bd850d6f3819d2183d8600b59cd3 Mon Sep 17 00:00:00 2001 From: gatecat Date: Wed, 12 May 2021 20:06:25 +0100 Subject: mistral: FF validity checking fixes Signed-off-by: gatecat --- mistral/lab.cc | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/mistral/lab.cc b/mistral/lab.cc index e93ef8a8..0719b37b 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -190,10 +190,16 @@ void Arch::create_lab(int x, int y) // Cell handling and annotation functions namespace { -ControlSig get_ctrlsig(const CellInfo *cell, IdString port) +ControlSig get_ctrlsig(const Context *ctx, const CellInfo *cell, IdString port, bool explicit_const = false) { ControlSig result; result.net = get_net_or_empty(cell, port); + if (result.net == nullptr && explicit_const) { + // For ENA, 1 (and 0) are explicit control set choices even though they aren't routed, as "no ENA" still + // consumes a clock+ENA pair + CellPinState st = PIN_1; + result.net = ctx->nets.at((st == PIN_1) ? ctx->id("$PACKER_VCC_NET") : ctx->id("$PACKER_GND_NET")).get(); + } if (cell->pin_data.count(port)) result.inverted = cell->pin_data.at(port).state == PIN_INV; else @@ -281,11 +287,11 @@ void Arch::assign_comb_info(CellInfo *cell) const 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.ctrlset.clk = get_ctrlsig(getCtx(), cell, id_CLK); + cell->ffInfo.ctrlset.ena = get_ctrlsig(getCtx(), cell, id_ENA, true); + cell->ffInfo.ctrlset.aclr = get_ctrlsig(getCtx(), cell, id_ACLR); + cell->ffInfo.ctrlset.sclr = get_ctrlsig(getCtx(), cell, id_SCLR); + cell->ffInfo.ctrlset.sload = get_ctrlsig(getCtx(), cell, id_SLOAD); cell->ffInfo.sdata = get_net_or_empty(cell, id_SDATA); cell->ffInfo.datain = get_net_or_empty(cell, id_DATAIN); } @@ -416,7 +422,7 @@ bool Arch::is_lab_ctrlset_legal(uint32_t lab) const { // Strictly speaking the constraint is up to 2 unique CLK and 3 CLK+ENA pairs. For now we simplify this to 1 CLK and // 3 ENA though. - ControlSig clk, sload, sclr; + ControlSig clk{}, sload{}, sclr{}; std::array aclr{}; std::array ena{}; -- cgit v1.2.3 From d39e67da7ed08b790fcc0b3c7ff9551164070fb2 Mon Sep 17 00:00:00 2001 From: gatecat Date: Wed, 12 May 2021 20:35:02 +0100 Subject: mistral: First pass at carry packing Signed-off-by: gatecat --- mistral/archdefs.h | 1 + mistral/constids.inc | 6 ++---- mistral/lab.cc | 42 ++++++++++++++++++++++++++++++++++++++---- mistral/pack.cc | 41 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/mistral/archdefs.h b/mistral/archdefs.h index ab00cb26..35b5bf63 100644 --- a/mistral/archdefs.h +++ b/mistral/archdefs.h @@ -183,6 +183,7 @@ struct ArchCellInfo : BaseClusterInfo int lut_bits_count; bool is_carry, is_shared, is_extended; + bool carry_start, carry_end; } combInfo; struct { diff --git a/mistral/constids.inc b/mistral/constids.inc index f1b51ee5..76d82a0b 100644 --- a/mistral/constids.inc +++ b/mistral/constids.inc @@ -39,9 +39,9 @@ X(Q) X(COMBOUT) X(SUM_OUT) -X(CIN) +X(CI) X(SHAREIN) -X(COUT) +X(CO) X(SHAREOUT) X(CARRY_START) @@ -64,8 +64,6 @@ X(LUT1) X(D0) X(D1) -X(CI) -X(CO) X(SO) X(WIRE) diff --git a/mistral/lab.cc b/mistral/lab.cc index 0719b37b..202753b4 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -48,7 +48,7 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) 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); + carry_in = arch->add_wire(x, y - 1, id_CO); share_in = arch->add_wire(x, y - 1, id_SHAREOUT); } } else { @@ -57,7 +57,7 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) 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); + carry_out = arch->add_wire(x, y, id_CO); share_out = arch->add_wire(x, y, id_SHAREOUT); } else { carry_out = arch->add_wire(x, y, arch->id(stringf("CARRY[%d]", z * 2 + i))); @@ -75,9 +75,9 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) 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_CI, 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_CO, PORT_OUT, carry_out); arch->add_bel_pin(bel, id_SHAREOUT, PORT_OUT, share_out); // Combinational output alm.comb_out[i] = arch->add_wire(x, y, arch->id(stringf("COMBOUT[%d]", z * 2 + i))); @@ -231,6 +231,8 @@ void Arch::assign_comb_info(CellInfo *cell) const cell->combInfo.is_carry = false; cell->combInfo.is_shared = false; cell->combInfo.is_extended = false; + cell->combInfo.carry_start = false; + cell->combInfo.carry_end = false; if (cell->type == id_MISTRAL_ALUT_ARITH) { cell->combInfo.is_carry = true; @@ -241,7 +243,13 @@ void Arch::assign_comb_info(CellInfo *cell) const for (auto pin : {id_A, id_B, id_C, id_D0, id_D1}) { cell->combInfo.lut_in[i++] = get_net_or_empty(cell, pin); } + + const NetInfo *ci = get_net_or_empty(cell, id_CI); + const NetInfo *co = get_net_or_empty(cell, id_CO); + cell->combInfo.comb_out = get_net_or_empty(cell, id_SO); + cell->combInfo.carry_start = (ci == nullptr) || (ci->driver.cell == nullptr); + cell->combInfo.carry_end = (co == nullptr) || (co->users.empty()); } else { cell->combInfo.lut_input_count = 0; switch (cell->type.index) { @@ -296,6 +304,22 @@ void Arch::assign_ff_info(CellInfo *cell) const cell->ffInfo.datain = get_net_or_empty(cell, id_DATAIN); } +namespace { +// Check if the other side of a carry chain wire is being used +bool carry_used(const Arch *arch, BelId bel, IdString pin) +{ + WireId wire = arch->getBelPinWire(bel, pin); + for (auto bp : arch->getWireBelPins(wire)) { + if (bp.bel == bel) + continue; + CellInfo *ci = arch->getBoundBelCell(bp.bel); + if (ci != nullptr && ci->combInfo.is_carry) + return true; + } + return false; +} +} // namespace + // Validity checking functions bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const { @@ -342,6 +366,16 @@ bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const return false; } + // Never allow two disjoint carry chains to accidentally stack + for (int i = 0; i < 2; i++) { + if (!luts[i]) + continue; + if (luts[i]->combInfo.carry_start && carry_used(this, alm_data.lut_bels[i], id_CI)) + return false; + if (luts[i]->combInfo.carry_end && carry_used(this, alm_data.lut_bels[i], id_CO)) + 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 diff --git a/mistral/pack.cc b/mistral/pack.cc index 78657321..aed9572e 100644 --- a/mistral/pack.cc +++ b/mistral/pack.cc @@ -275,11 +275,52 @@ struct MistralPacker } } + void constrain_carries() + { + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_MISTRAL_ALUT_ARITH) + continue; + const NetInfo *cin = get_net_or_empty(ci, id_CI); + if (cin != nullptr && cin->driver.cell != nullptr) + continue; // not the start of a chain + std::vector chain; + CellInfo *cursor = ci; + while (true) { + chain.push_back(cursor); + const NetInfo *co = get_net_or_empty(cursor, id_CO); + if (co == nullptr || co->users.empty()) + break; + if (co->users.size() > 1) + log_error("Carry net %s has more than one sink!\n", ctx->nameOf(co)); + auto &usr = co->users.at(0); + if (usr.port != id_CI) + log_error("Carry net %s drives port %s, expected CI\n", ctx->nameOf(co), ctx->nameOf(usr.port)); + cursor = usr.cell; + } + + chain.at(0)->constr_abs_z = true; + chain.at(0)->constr_z = 0; + chain.at(0)->cluster = chain.at(0)->name; + + for (int i = 1; i < int(chain.size()); i++) { + chain.at(i)->constr_x = 0; + chain.at(i)->constr_y = (i / 20); + // 2 COMB, 4 FF per ALM + chain.at(i)->constr_z = ((i / 2) % 10) * 6 + (i % 2); + chain.at(i)->constr_abs_z = true; + chain.at(i)->cluster = chain.at(0)->name; + chain.at(0)->constr_children.push_back(chain.at(i)); + } + } + } + void run() { init_constant_nets(); pack_constants(); pack_io(); + constrain_carries(); } }; }; // namespace -- cgit v1.2.3 From 2f2fde7e6cd687433c687b1f678ceb4bd25f588e Mon Sep 17 00:00:00 2001 From: gatecat Date: Wed, 12 May 2021 20:41:52 +0100 Subject: mistral: Write arith mode to bitstream (not yet functional) Signed-off-by: gatecat --- mistral/bitstream.cc | 3 +++ mistral/lab.cc | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index 3337c38e..713ca9bb 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -222,6 +222,9 @@ struct MistralBitgen cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::BDFF0, alm, CycloneV::NLUT); cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::BDFF1, alm, CycloneV::NLUT); cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::BDFF1L, alm, CycloneV::NLUT); + + if ((luts[0] && luts[0]->combInfo.is_carry) || (luts[1] && luts[1]->combInfo.is_carry)) + cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::ARITH_SEL, alm, CycloneV::ADDER); } void write_labs() diff --git a/mistral/lab.cc b/mistral/lab.cc index 202753b4..77bdac09 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -366,21 +366,34 @@ bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const return false; } - // Never allow two disjoint carry chains to accidentally stack + bool carry_mode = false; + for (int i = 0; i < 2; i++) { if (!luts[i]) continue; + if (!luts[i]->combInfo.is_carry) + continue; + carry_mode = true; + // Never allow two disjoint carry chains to accidentally stack if (luts[i]->combInfo.carry_start && carry_used(this, alm_data.lut_bels[i], id_CI)) return false; if (luts[i]->combInfo.carry_end && carry_used(this, alm_data.lut_bels[i], id_CO)) return false; } + for (int i = 0; i < 2; i++) { + if (!luts[i]) + continue; + // No mixing of carry and non-carry + if (luts[i]->combInfo.is_carry != carry_mode) + 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); + bool route_thru_lut_avail = !luts[i] && !carry_mode && (total_lut_inputs < 8) && (used_lut_bits < 64); // E/F is available if this LUT is using 3 or fewer inputs - this is conservative and sharing can probably // improve this situation bool ef_available = (!luts[i] || luts[i]->combInfo.used_lut_input_count <= 3); -- cgit v1.2.3 From 3d1bb4f1b217b020e750277dd1ee2e4d3516b58f Mon Sep 17 00:00:00 2001 From: gatecat Date: Wed, 12 May 2021 21:21:33 +0100 Subject: mistral: Carry debugging Signed-off-by: gatecat --- mistral/bitstream.cc | 5 ++++- mistral/lab.cc | 45 ++++++--------------------------------------- mistral/pack.cc | 2 +- 3 files changed, 11 insertions(+), 41 deletions(-) diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index 713ca9bb..a9eaeff2 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -223,8 +223,11 @@ struct MistralBitgen cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::BDFF1, alm, CycloneV::NLUT); cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::BDFF1L, alm, CycloneV::NLUT); - if ((luts[0] && luts[0]->combInfo.is_carry) || (luts[1] && luts[1]->combInfo.is_carry)) + bool is_carry = (luts[0] && luts[0]->combInfo.is_carry) || (luts[1] && luts[1]->combInfo.is_carry); + if (is_carry) cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::ARITH_SEL, alm, CycloneV::ADDER); + if (is_carry && alm == 0 && luts[0]->combInfo.carry_start) + cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::TTO_DIS, alm, true); } void write_labs() diff --git a/mistral/lab.cc b/mistral/lab.cc index 77bdac09..a0179a88 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -42,14 +42,14 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) WireId carry_in, share_in; WireId carry_out, share_out; if (z == 0 && i == 0) { - if (y == 0) { + if (y == arch->getGridDimY() - 1) { // 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_CO); - share_in = arch->add_wire(x, y - 1, id_SHAREOUT); + carry_in = arch->add_wire(x, y + 1, id_CO); + share_in = arch->add_wire(x, y + 1, id_SHAREOUT); } } else { // Output from last combinational unit @@ -304,22 +304,6 @@ void Arch::assign_ff_info(CellInfo *cell) const cell->ffInfo.datain = get_net_or_empty(cell, id_DATAIN); } -namespace { -// Check if the other side of a carry chain wire is being used -bool carry_used(const Arch *arch, BelId bel, IdString pin) -{ - WireId wire = arch->getBelPinWire(bel, pin); - for (auto bp : arch->getWireBelPins(wire)) { - if (bp.bel == bel) - continue; - CellInfo *ci = arch->getBoundBelCell(bp.bel); - if (ci != nullptr && ci->combInfo.is_carry) - return true; - } - return false; -} -} // namespace - // Validity checking functions bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const { @@ -368,26 +352,9 @@ bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const bool carry_mode = false; - for (int i = 0; i < 2; i++) { - if (!luts[i]) - continue; - if (!luts[i]->combInfo.is_carry) - continue; - carry_mode = true; - // Never allow two disjoint carry chains to accidentally stack - if (luts[i]->combInfo.carry_start && carry_used(this, alm_data.lut_bels[i], id_CI)) - return false; - if (luts[i]->combInfo.carry_end && carry_used(this, alm_data.lut_bels[i], id_CO)) - return false; - } - - for (int i = 0; i < 2; i++) { - if (!luts[i]) - continue; - // No mixing of carry and non-carry - if (luts[i]->combInfo.is_carry != carry_mode) - return false; - } + // No mixing of carry and non-carry + if (luts[0] && luts[1] && luts[0]->combInfo.is_carry != luts[1]->combInfo.is_carry) + return false; // For each ALM half; check FF control set sharing and input routeability for (int i = 0; i < 2; i++) { diff --git a/mistral/pack.cc b/mistral/pack.cc index aed9572e..b86393d7 100644 --- a/mistral/pack.cc +++ b/mistral/pack.cc @@ -305,7 +305,7 @@ struct MistralPacker for (int i = 1; i < int(chain.size()); i++) { chain.at(i)->constr_x = 0; - chain.at(i)->constr_y = (i / 20); + chain.at(i)->constr_y = -(i / 20); // 2 COMB, 4 FF per ALM chain.at(i)->constr_z = ((i / 2) % 10) * 6 + (i % 2); chain.at(i)->constr_abs_z = true; -- cgit v1.2.3 From 09a867310b8c3c51e62f394a9acfda217e7addc5 Mon Sep 17 00:00:00 2001 From: gatecat Date: Thu, 13 May 2021 20:25:55 +0100 Subject: mistral: Carry fixes Signed-off-by: gatecat --- mistral/bitstream.cc | 3 ++- mistral/lab.cc | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index a9eaeff2..58154d7c 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -226,7 +226,8 @@ struct MistralBitgen bool is_carry = (luts[0] && luts[0]->combInfo.is_carry) || (luts[1] && luts[1]->combInfo.is_carry); if (is_carry) cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::ARITH_SEL, alm, CycloneV::ADDER); - if (is_carry && alm == 0 && luts[0]->combInfo.carry_start) + // The carry in/out enable bits + if (is_carry && alm == 0 && !luts[0]->combInfo.carry_start) cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::TTO_DIS, alm, true); } diff --git a/mistral/lab.cc b/mistral/lab.cc index a0179a88..11b347ca 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -735,11 +735,23 @@ uint64_t Arch::compute_lut_mask(uint32_t lab, uint8_t alm) int index = 0; for (int k = 0; k < lut->combInfo.lut_input_count; k++) { IdString log_pin = get_lut_pin(lut, k); + int init_idx = k; + if (arith) { + // D0 only affects lower half; D1 upper half + if (k == 3 && j >= 16) + continue; + if (k == 4) { + if (j < 16) + continue; + else + init_idx = 3; + } + } CellPinState state = lut->get_pin_state(log_pin); if (state == PIN_0) continue; else if (state == PIN_1) - index |= (1 << k); + index |= (1 << init_idx); // Ignore if no associated physical pin if (get_net_or_empty(lut, log_pin) == nullptr || lut->pin_data.at(log_pin).bel_pins.empty()) continue; @@ -749,7 +761,7 @@ uint64_t Arch::compute_lut_mask(uint32_t lab, uint8_t alm) // Depermute physical pin IdString phys_pin = lut->pin_data.at(log_pin).bel_pins.at(0); if (get_phys_pin_val(alm_data.l6_mode, arith, j, phys_pin) != inverted) - index |= (1 << k); + index |= (1 << init_idx); } if ((init >> index) & 0x1) { mask |= (1ULL << uint64_t(j + offset)); -- cgit v1.2.3 From 3313d5267a4e4609d694d15e66040b0829788bb4 Mon Sep 17 00:00:00 2001 From: gatecat Date: Thu, 13 May 2021 22:47:29 +0100 Subject: mistral: Adding FF control set reservation Signed-off-by: gatecat --- mistral/arch.cc | 17 +++++++ mistral/arch.h | 34 ++++++++++--- mistral/lab.cc | 155 +++++++++++++++++++++++++++++++++++++------------------- 3 files changed, 148 insertions(+), 58 deletions(-) diff --git a/mistral/arch.cc b/mistral/arch.cc index 5fc2a0b4..ffe6e833 100644 --- a/mistral/arch.cc +++ b/mistral/arch.cc @@ -305,6 +305,23 @@ WireId Arch::add_wire(int x, int y, IdString name, uint64_t flags) } } +void Arch::reserve_route(WireId src, WireId dst) +{ + auto &dst_data = wires.at(dst); + int idx = -1; + + for (int i = 0; i < int(dst_data.wires_uphill.size()); i++) { + if (dst_data.wires_uphill.at(i) == src) { + idx = i; + break; + } + } + + NPNR_ASSERT(idx != -1); + + dst_data.flags = WireInfo::RESERVED_ROUTE | unsigned(idx); +} + bool Arch::wires_connected(WireId src, WireId dst) const { PipId pip(src.node, dst.node); diff --git a/mistral/arch.h b/mistral/arch.h index e22d26fd..c0ab86e5 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -106,6 +106,10 @@ struct WireInfo // flags for special wires (currently unused) uint64_t flags; + + // if the RESERVED_ROUTE mask is set in flags, then only wires_uphill[flags&0xFF] may drive this wire - used for + // control set preallocations + static const uint64_t RESERVED_ROUTE = 0x100; }; // This transforms a WireIds, and adds the mising half of the pair to create a PipId @@ -259,13 +263,8 @@ enum CellPinStyle PINSTYLE_COMB = 0x017, // combinational signal, defaults low, can be inverted and tied PINSTYLE_CLK = 0x107, // CLK type signal, invertible and defaults to disconnected - // Technically speaking CE and RSTs should be invertible, too. But we don't use this currently due to the possible - // need to route one CE to two different LAB wires if both inverted and non-inverted variants are used in the same - // LAB This should be acheiveable by prerouting the LAB wiring inside assign_control_sets, but let's pass on this - // for a first attempt. - - PINSTYLE_CE = 0x023, // CE type signal, ~~invertible~~ and defaults to enabled - PINSTYLE_RST = 0x013, // RST type signal, ~~invertible~~ and defaults to not reset + PINSTYLE_CE = 0x027, // CE type signal, invertible and defaults to enabled + PINSTYLE_RST = 0x017, // RST type signal, invertible and defaults to not reset PINSTYLE_DEDI = 0x000, // dedicated signals, leave alone PINSTYLE_INP = 0x001, // general inputs, no inversion/tieing but defaults low PINSTYLE_PU = 0x022, // signals that float high and default high @@ -337,6 +336,8 @@ struct Arch : BaseArch AllWireRange getWires() const override { return AllWireRange(wires); } bool wires_connected(WireId src, WireId dst) const; + // Only allow src, and not any other wire, to drive dst + void reserve_route(WireId src, WireId dst); // ------------------------------------------------- @@ -356,6 +357,25 @@ struct Arch : BaseArch return UpDownhillPipRange(wires.at(wire).wires_uphill, wire, true); } + bool checkPipAvail(PipId pip) const override + { + // Check reserved routes + WireId dst(pip.dst); + const auto &dst_data = wires.at(dst); + if ((dst_data.flags & WireInfo::RESERVED_ROUTE) != 0) { + if (WireId(pip.src) != dst_data.wires_uphill.at(dst_data.flags & 0xFF)) + return false; + } + return BaseArch::checkPipAvail(pip); + } + + bool checkPipAvailForNet(PipId pip, NetInfo *net) const override + { + if (!checkPipAvail(pip)) + return false; + return BaseArch::checkPipAvailForNet(pip, net); + } + // ------------------------------------------------- delay_t estimateDelay(WireId src, WireId dst) const override; diff --git a/mistral/lab.cc b/mistral/lab.cc index 11b347ca..7d798cbc 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -430,67 +430,82 @@ template bool check_assign_sig(std::array &sig_set, co } return false; }; -}; // namespace -bool Arch::is_lab_ctrlset_legal(uint32_t lab) const +// DATAIN mapping rules - which LAB DATAIN signals can be used for ENA and ACLR +static constexpr std::array ena_datain{2, 3, 0}; +static constexpr std::array aclr_datain{3, 2}; + +struct LabCtrlSetWorker { - // Strictly speaking the constraint is up to 2 unique CLK and 3 CLK+ENA pairs. For now we simplify this to 1 CLK and - // 3 ENA though. + ControlSig clk{}, sload{}, sclr{}; std::array aclr{}; std::array ena{}; - for (uint8_t alm = 0; alm < 10; alm++) { - for (uint8_t i = 0; i < 4; i++) { - const CellInfo *ff = getBoundBelCell(labs.at(lab).alms.at(alm).ff_bels.at(i)); - if (ff == nullptr) - continue; + std::array datain{}; - if (!check_assign_sig(clk, ff->ffInfo.ctrlset.clk)) - return false; - if (!check_assign_sig(sload, ff->ffInfo.ctrlset.sload)) - return false; - if (!check_assign_sig(sclr, ff->ffInfo.ctrlset.sclr)) - return false; - if (!check_assign_sig(aclr, ff->ffInfo.ctrlset.aclr)) - return false; - if (!check_assign_sig(ena, ff->ffInfo.ctrlset.ena)) + bool run(const Arch *arch, uint32_t lab) + { + // Strictly speaking the constraint is up to 2 unique CLK and 3 CLK+ENA pairs. For now we simplify this to 1 CLK + // and 3 ENA though. + for (uint8_t alm = 0; alm < 10; alm++) { + for (uint8_t i = 0; i < 4; i++) { + const CellInfo *ff = arch->getBoundBelCell(arch->labs.at(lab).alms.at(alm).ff_bels.at(i)); + if (ff == nullptr) + continue; + + if (!check_assign_sig(clk, ff->ffInfo.ctrlset.clk)) + return false; + if (!check_assign_sig(sload, ff->ffInfo.ctrlset.sload)) + return false; + if (!check_assign_sig(sclr, ff->ffInfo.ctrlset.sclr)) + return false; + if (!check_assign_sig(aclr, ff->ffInfo.ctrlset.aclr)) + return false; + if (!check_assign_sig(ena, ff->ffInfo.ctrlset.ena)) + return false; + } + } + // Check for overuse of the shared, LAB-wide datain signals + if (clk.net != nullptr && !clk.net->is_global) + if (!check_assign_sig(datain[0], clk)) // CLK only needs DATAIN[0] if it's not global return false; + if (!check_assign_sig(datain[1], sload)) + return false; + if (!check_assign_sig(datain[3], sclr)) + return false; + for (const auto &aclr_sig : aclr) { + // Check both possibilities that ACLR can map to + // TODO: ACLR could be global, too + if (check_assign_sig(datain[aclr_datain[0]], aclr_sig)) + continue; + if (check_assign_sig(datain[aclr_datain[1]], aclr_sig)) + continue; + // Failed to find any free ACLR-capable DATAIN + return false; } - } - - // Check for overuse of the shared, LAB-wide datain signals - std::array datain{}; - if (clk.net != nullptr && !clk.net->is_global) - if (!check_assign_sig(datain[0], clk)) // CLK only needs DATAIN[0] if it's not global + for (const auto &ena_sig : ena) { + // Check all 3 possibilities that ACLR can map to + // TODO: ACLR could be global, too + if (check_assign_sig(datain[ena_datain[0]], ena_sig)) + continue; + if (check_assign_sig(datain[ena_datain[1]], ena_sig)) + continue; + if (check_assign_sig(datain[ena_datain[2]], ena_sig)) + continue; + // Failed to find any free ENA-capable DATAIN return false; - if (!check_assign_sig(datain[1], sload)) - return false; - if (!check_assign_sig(datain[3], sclr)) - return false; - for (const auto &aclr_sig : aclr) { - // Check both possibilities that ACLR can map to - // TODO: ACLR could be global, too - if (check_assign_sig(datain[3], aclr_sig)) - continue; - if (check_assign_sig(datain[2], aclr_sig)) - continue; - // Failed to find any free ACLR-capable DATAIN - return false; - } - for (const auto &ena_sig : ena) { - // Check all 3 possibilities that ACLR can map to - // TODO: ACLR could be global, too - if (check_assign_sig(datain[2], ena_sig)) - continue; - if (check_assign_sig(datain[3], ena_sig)) - continue; - if (check_assign_sig(datain[0], ena_sig)) - continue; - // Failed to find any free ENA-capable DATAIN - return false; + } + return true; } - return true; +}; + +}; // namespace + +bool Arch::is_lab_ctrlset_legal(uint32_t lab) const +{ + LabCtrlSetWorker worker; + return worker.run(this, lab); } void Arch::lab_pre_route() @@ -506,9 +521,47 @@ void Arch::lab_pre_route() void Arch::assign_control_sets(uint32_t lab) { - // TODO: set up reservations for checkPipAvailForNet for control set signals + // Set up reservations for checkPipAvail for control set signals // This will be needed because clock and CE are routed together and must be kept together, there isn't free choice // e.g. CLK0 & ENA0 must be use for one control set, and CLK1 & ENA1 for another, they can't be mixed and matched + // Similarly for how inverted & noninverted variants must be kept separate + LabCtrlSetWorker worker; + bool legal = worker.run(this, lab); + NPNR_ASSERT(legal); + auto &lab_data = labs.at(lab); + for (uint8_t alm = 0; alm < 10; alm++) { + for (uint8_t i = 0; i < 4; i++) { + BelId ff_bel = lab_data.alms.at(alm).ff_bels.at(i); + const CellInfo *ff = getBoundBelCell(ff_bel); + if (ff == nullptr) + continue; + ControlSig ena_sig = ff->ffInfo.ctrlset.ena; + WireId ena_wire = getBelPinWire(ff_bel, id_ENA); + for (int i = 0; i < 3; i++) { + if (ena_sig == worker.datain[ena_datain[i]]) { + if (getCtx()->debug) { + log_info("Assigned CLK/ENA set %d to FF %s (%s)\n", i, nameOf(ff), getCtx()->nameOfBel(ff_bel)); + } + reserve_route(lab_data.ena_wires[i], ena_wire); + // TODO: lock clock according to ENA choice, too + break; + } + } + + ControlSig aclr_sig = ff->ffInfo.ctrlset.aclr; + WireId aclr_wire = getBelPinWire(ff_bel, id_ACLR); + for (int i = 0; i < 2; i++) { + // TODO: could be global ACLR, too + if (aclr_sig == worker.datain[aclr_datain[i]]) { + if (getCtx()->debug) { + log_info("Assigned ACLR set %d to FF %s (%s)\n", i, nameOf(ff), getCtx()->nameOfBel(ff_bel)); + } + reserve_route(lab_data.aclr_wires[i], aclr_wire); + break; + } + } + } + } } namespace { -- cgit v1.2.3 From e688ee0e89263bc39a0ea10ac552430e41824aa1 Mon Sep 17 00:00:00 2001 From: gatecat Date: Thu, 13 May 2021 23:46:08 +0100 Subject: mistral: Debugging carry chain issues Signed-off-by: gatecat --- mistral/lab.cc | 29 ++++++++++++++++------------- mistral/pack.cc | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/mistral/lab.cc b/mistral/lab.cc index 7d798cbc..2d57ed42 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -42,20 +42,20 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) WireId carry_in, share_in; WireId carry_out, share_out; if (z == 0 && i == 0) { - if (y == arch->getGridDimY() - 1) { - // 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_CO); - share_in = arch->add_wire(x, y + 1, id_SHAREOUT); + carry_in = arch->add_wire(x, y, id_CI); + share_in = arch->add_wire(x, y, id_SHAREIN); + if (y < (arch->getGridDimY() - 1)) { + // Carry is split at tile boundary (TTO_DIS bit), add a PIP to represent this. + // TODO: what about BTO_DIS, in the middle of the LAB? + arch->add_pip(arch->add_wire(x, y + 1, id_CO), carry_in); + arch->add_pip(arch->add_wire(x, y + 1, id_SHAREOUT), share_in); } } else { // Output from last combinational unit carry_in = arch->add_wire(x, y, arch->id(stringf("CARRY[%d]", (z * 2 + i) - 1))); share_in = arch->add_wire(x, y, arch->id(stringf("SHARE[%d]", (z * 2 + i) - 1))); } + if (z == 9 && i == 1) { carry_out = arch->add_wire(x, y, id_CO); share_out = arch->add_wire(x, y, id_SHAREOUT); @@ -582,7 +582,7 @@ static void assign_lut6_inputs(CellInfo *cell, int lut) if (!cell->ports.count(log) || cell->ports.at(log).net == nullptr) continue; cell->pin_data[log].bel_pins.clear(); - cell->pin_data[log].bel_pins.push_back(phys_pins.at(++phys_idx)); + cell->pin_data[log].bel_pins.push_back(phys_pins.at(phys_idx++)); } } } // namespace @@ -640,16 +640,19 @@ void Arch::reassign_alm_inputs(uint32_t lab, uint8_t alm) continue; // Work out which physical ports are available std::vector avail_phys_ports; + // D/C always available and dedicated to the half, in L5 mode avail_phys_ports.push_back((i == 1) ? id_D : id_C); - if (b_avail) - avail_phys_ports.push_back(id_B); - if (a_avail) - avail_phys_ports.push_back(id_A); // In arithmetic mode, Ei can only be used for D0 and Fi can only be used for D1 + // otherwise, these are general and dedicated to one half if (!luts[i]->combInfo.is_carry) { avail_phys_ports.push_back((i == 1) ? id_E1 : id_E0); avail_phys_ports.push_back((i == 1) ? id_F1 : id_F0); } + // A and B might be used for shared signals, or already used by the other half + if (b_avail) + avail_phys_ports.push_back(id_B); + if (a_avail) + avail_phys_ports.push_back(id_A); int phys_idx = 0; for (int j = 0; j < luts[i]->combInfo.lut_input_count; j++) { diff --git a/mistral/pack.cc b/mistral/pack.cc index b86393d7..f8c55cd1 100644 --- a/mistral/pack.cc +++ b/mistral/pack.cc @@ -312,6 +312,24 @@ struct MistralPacker chain.at(i)->cluster = chain.at(0)->name; chain.at(0)->constr_children.push_back(chain.at(i)); } + + if (ctx->debug) { + log_info("Chain: \n"); + for (int i = 0; i < int(chain.size()); i++) { + auto &c = chain.at(i); + log_info(" i=%d cell=%s dy=%d z=%d ci=%s co=%s\n", i, ctx->nameOf(c), c->constr_y, c->constr_z, + ctx->nameOf(get_net_or_empty(c, id_CI)), ctx->nameOf(get_net_or_empty(c, id_CO))); + } + } + } + // Check we reached all the cells in the above pass + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_MISTRAL_ALUT_ARITH) + continue; + if (ci->cluster == ClusterId()) + log_error("Failed to include arith cell '%s' in any chain (CI=%s)\n", ctx->nameOf(ci), + ctx->nameOf(get_net_or_empty(ci, id_CI))); } } -- cgit v1.2.3 From 6ad329c5404021ca38784b21fc4d01a548caed30 Mon Sep 17 00:00:00 2001 From: gatecat Date: Thu, 13 May 2021 23:51:47 +0100 Subject: mistral: Implement bounding boxes for router2 Signed-off-by: gatecat --- mistral/arch.cc | 14 ++++++++++++++ mistral/arch.h | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/mistral/arch.cc b/mistral/arch.cc index ffe6e833..d5bc424c 100644 --- a/mistral/arch.cc +++ b/mistral/arch.cc @@ -382,6 +382,20 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const return 100 * std::abs(y1 - y0) + 100 * std::abs(x1 - x0) + 100; } +ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const +{ + ArcBounds bounds; + int src_x = CycloneV::rn2x(src.node); + int src_y = CycloneV::rn2y(src.node); + int dst_x = CycloneV::rn2x(dst.node); + int dst_y = CycloneV::rn2y(dst.node); + bounds.x0 = std::min(src_x, dst_x); + bounds.y0 = std::min(src_y, dst_y); + bounds.x1 = std::max(src_x, dst_x); + bounds.y1 = std::max(src_y, dst_y); + return bounds; +} + delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const { if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId()) diff --git a/mistral/arch.h b/mistral/arch.h index c0ab86e5..3d5a8617 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -386,7 +386,7 @@ struct Arch : BaseArch 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(); } + ArcBounds getRouteBoundingBox(WireId src, WireId dst) const override; // ------------------------------------------------- -- cgit v1.2.3 From 8c7fa8e6c956686927f3ff92088238105c09b8c3 Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 14 May 2021 00:02:16 +0100 Subject: mistral: Implement PIP locations, too Signed-off-by: gatecat --- mistral/arch.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mistral/arch.h b/mistral/arch.h index 3d5a8617..4e603177 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -343,7 +343,7 @@ struct Arch : BaseArch PipId getPipByName(IdStringList name) const override; AllPipRange getPips() const override { return AllPipRange(wires); } - Loc getPipLocation(PipId pip) const override { return Loc(0, 0, 0); } + Loc getPipLocation(PipId pip) const override { return Loc(CycloneV::rn2x(pip.dst), CycloneV::rn2y(pip.dst), 0); } IdStringList getPipName(PipId pip) const override; WireId getPipSrcWire(PipId pip) const override { return WireId(pip.src); }; WireId getPipDstWire(PipId pip) const override { return WireId(pip.dst); }; -- cgit v1.2.3 From bd525d3548df30b5e4831348fdad7fe5fd5180c2 Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 14 May 2021 14:58:56 +0100 Subject: msitral: Fix pip iterator Python bindings Signed-off-by: gatecat --- mistral/arch_pybindings.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mistral/arch_pybindings.cc b/mistral/arch_pybindings.cc index d93b7e28..23716c93 100644 --- a/mistral/arch_pybindings.cc +++ b/mistral/arch_pybindings.cc @@ -61,8 +61,8 @@ void arch_wrap_python(py::module &m) typedef const std::vector &BelRange; - typedef const UpDownhillPipRange UphillPipRange; - typedef const UpDownhillPipRange DownhillPipRange; + typedef UpDownhillPipRange UphillPipRange; + typedef UpDownhillPipRange DownhillPipRange; typedef AllWireRange WireRange; typedef const std::vector &BelPinRange; -- cgit v1.2.3 From b2f45b1aab596d4185f3a6c6a60d6b97c6aa4ad0 Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 14 May 2021 18:26:51 +0100 Subject: mistral: Account for TD input count limit Signed-off-by: gatecat --- mistral/arch.cc | 14 +++++++-- mistral/arch.h | 26 ++++++++++++++-- mistral/archdefs.h | 7 +++++ mistral/lab.cc | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 128 insertions(+), 9 deletions(-) diff --git a/mistral/arch.cc b/mistral/arch.cc index d5bc424c..9c661439 100644 --- a/mistral/arch.cc +++ b/mistral/arch.cc @@ -149,15 +149,23 @@ IdStringList Arch::getBelName(BelId bel) const 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); + return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && check_lab_input_count(data.lab_data.lab); } else if (data.type == id_MISTRAL_FF) { - return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && is_lab_ctrlset_legal(data.lab_data.lab); + return is_alm_legal(data.lab_data.lab, data.lab_data.alm) && check_lab_input_count(data.lab_data.lab) && + is_lab_ctrlset_legal(data.lab_data.lab); } return true; } +void Arch::update_bel(BelId bel) +{ + auto &data = bel_data(bel); + if (data.type == id_MISTRAL_COMB || data.type == id_MISTRAL_FF) { + update_alm_input_count(data.lab_data.lab, data.lab_data.alm); + } +} + WireId Arch::getWireByName(IdStringList name) const { // non-mistral wires diff --git a/mistral/arch.h b/mistral/arch.h index 4e603177..97d6a2b1 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -49,6 +49,12 @@ struct ALMInfo std::array ff_bels; bool l6_mode = false; + + // Which CLK/ENA and ACLR is chosen for each half + std::array clk_ena_idx, aclr_idx; + + // For keeping track of how many inputs are currently being used, for the LAB routeability check + int unique_input_count = 0; }; struct LABInfo @@ -325,6 +331,18 @@ struct Arch : BaseArch bool isBelLocationValid(BelId bel) const override; + void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) override + { + BaseArch::bindBel(bel, cell, strength); + update_bel(bel); + } + void unbindBel(BelId bel) override + { + BaseArch::unbindBel(bel); + update_bel(bel); + } + + void update_bel(BelId bel); BelId bel_by_block_idx(int x, int y, IdString type, int block_index) const; // ------------------------------------------------- @@ -428,13 +446,15 @@ struct Arch : BaseArch bool is_comb_cell(IdString cell_type) const; // lab.cc bool is_alm_legal(uint32_t lab, uint8_t alm) const; // lab.cc bool is_lab_ctrlset_legal(uint32_t lab) const; // lab.cc + bool check_lab_input_count(uint32_t lab) const; // lab.cc void assign_comb_info(CellInfo *cell) const; // lab.cc void assign_ff_info(CellInfo *cell) const; // lab.cc - void lab_pre_route(); // lab.cc - void assign_control_sets(uint32_t lab); // lab.cc - void reassign_alm_inputs(uint32_t lab, uint8_t alm); // lab.cc + void lab_pre_route(); // lab.cc + void assign_control_sets(uint32_t lab); // lab.cc + void reassign_alm_inputs(uint32_t lab, uint8_t alm); // lab.cc + void update_alm_input_count(uint32_t lab, uint8_t alm); // lab.cc uint64_t compute_lut_mask(uint32_t lab, uint8_t alm); // lab.cc diff --git a/mistral/archdefs.h b/mistral/archdefs.h index 35b5bf63..181bc618 100644 --- a/mistral/archdefs.h +++ b/mistral/archdefs.h @@ -182,6 +182,13 @@ struct ArchCellInfo : BaseClusterInfo int used_lut_input_count; // excluding those null/constant int lut_bits_count; + // for the LAB routeability check (see the detailed description in lab.cc); usually the same signal feeding + // multiple ALMs in a LAB is counted multiple times, due to not knowing which routing resources it will need + // in each case. But carry chains where we know how things will pack are allowed to share across ALMs as a + // special case, primarily to support adders/subtractors with a 'B invert' control signal shared across all + // ALMs. + int chain_shared_input_count; + bool is_carry, is_shared, is_extended; bool carry_start, carry_end; } combInfo; diff --git a/mistral/lab.cc b/mistral/lab.cc index 2d57ed42..3e6292b3 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -233,15 +233,20 @@ void Arch::assign_comb_info(CellInfo *cell) const cell->combInfo.is_extended = false; cell->combInfo.carry_start = false; cell->combInfo.carry_end = false; + cell->combInfo.chain_shared_input_count = 0; if (cell->type == id_MISTRAL_ALUT_ARITH) { cell->combInfo.is_carry = true; cell->combInfo.lut_input_count = 5; cell->combInfo.lut_bits_count = 32; + // This is a special case in terms of naming - 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); + const std::array arith_pins{id_A, id_B, id_C, id_D0, id_D1}; + { + int i = 0; + for (auto pin : arith_pins) { + cell->combInfo.lut_in[i++] = get_net_or_empty(cell, pin); + } } const NetInfo *ci = get_net_or_empty(cell, id_CI); @@ -250,6 +255,22 @@ void Arch::assign_comb_info(CellInfo *cell) const cell->combInfo.comb_out = get_net_or_empty(cell, id_SO); cell->combInfo.carry_start = (ci == nullptr) || (ci->driver.cell == nullptr); cell->combInfo.carry_end = (co == nullptr) || (co->users.empty()); + + // Compute cross-ALM routing sharing - only check the z=0 case inside ALMs + if (cell->constr_z > 0 && ((cell->constr_z % 2) == 0) && ci) { + const CellInfo *prev = ci->driver.cell; + if (prev != nullptr) { + for (int i = 0; i < 5; i++) { + const NetInfo *a = get_net_or_empty(cell, arith_pins[i]); + if (a == nullptr) + continue; + const NetInfo *b = get_net_or_empty(prev, arith_pins[i]); + if (a == b) + ++cell->combInfo.chain_shared_input_count; + } + } + } + } else { cell->combInfo.lut_input_count = 0; switch (cell->type.index) { @@ -402,6 +423,69 @@ bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const return true; } +void Arch::update_alm_input_count(uint32_t lab, uint8_t alm) +{ + // TODO: duplication with above + auto &alm_data = labs.at(lab).alms.at(alm); + // Get cells into an array for fast access + std::array 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 total_inputs = 0; + int total_lut_inputs = 0; + for (int i = 0; i < 2; i++) { + if (!luts[i]) + continue; + total_lut_inputs += luts[i]->combInfo.used_lut_input_count - luts[i]->combInfo.chain_shared_input_count; + } + int shared_lut_inputs = 0; + if (luts[0] && luts[1]) { + for (int i = 0; i < luts[1]->combInfo.lut_input_count; i++) { + const NetInfo *sig = luts[1]->combInfo.lut_in[i]; + if (!sig) + continue; + for (int j = 0; j < luts[0]->combInfo.lut_input_count; j++) { + if (sig == luts[0]->combInfo.lut_in[j]) { + ++shared_lut_inputs; + break; + } + } + if (shared_lut_inputs >= 2) { + // only 2 inputs have guaranteed sharing, without routeability based LUT permutation at least + break; + } + } + } + total_inputs = std::max(0, total_lut_inputs - shared_lut_inputs); + for (int i = 0; i < 4; i++) { + const CellInfo *ff = ffs[i]; + if (!ff) + continue; + if (ff->ffInfo.sdata) + ++total_inputs; + // FF input doesn't consume routing resources if driven by associated LUT + if (ff->ffInfo.datain && (!luts[i / 2] || ff->ffInfo.datain != luts[i / 2]->combInfo.comb_out)) + ++total_inputs; + } + alm_data.unique_input_count = total_inputs; +} + +bool Arch::check_lab_input_count(uint32_t lab) const +{ + // There are only 46 TD signals available to route signals from general routing to the ALM input. Currently, we + // check the total sum of ALM inputs is less than 42; 46 minus 4 FF control inputs. This is a conservative check for + // several reasons, because LD signals are also available for feedback routing from ALM output to input, and because + // TD signals may be shared if the same net routes to multiple ALMs. But these cases will need careful handling and + // LUT permutation during routing to be useful; and in any event conservative LAB packing will help nextpnr's + // currently perfunctory place and route algorithms to achieve satisfactory runtimes. + int count = 0; + auto &lab_data = labs.at(lab); + for (int i = 0; i < 10; i++) { + count += lab_data.alms.at(i).unique_input_count; + } + return (count <= 42); +} + namespace { bool check_assign_sig(ControlSig &sig_set, const ControlSig &sig) { -- cgit v1.2.3 From 66b3a192f8966538d32d3467dbb160ae31e2ee9e Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 14 May 2021 19:58:36 +0100 Subject: mistral: First pass at FF and CLKBUF bitgen Signed-off-by: gatecat --- mistral/bitstream.cc | 112 ++++++++++++++++++++++++++++++++++++++++++++++----- mistral/lab.cc | 21 ++++++---- 2 files changed, 115 insertions(+), 18 deletions(-) diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index 58154d7c..7885f959 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -185,6 +185,14 @@ struct MistralBitgen true); } + void write_clkbuf_cell(CellInfo *ci, int x, int y, int bi) + { + (void)ci; // currently unused + auto pos = CycloneV::xy2pos(x, y); + cv->bmux_n_set(CycloneV::CMUXHG, pos, CycloneV::INPUT_SELECT, bi, 0x1b); // hardcode to general routing + cv->bmux_m_set(CycloneV::CMUXHG, pos, CycloneV::TESTSYN_ENOUT_SELECT, bi, CycloneV::PRE_SYNENB); + } + void write_cells() { for (auto cell : sorted(ctx->cells)) { @@ -193,10 +201,12 @@ struct MistralBitgen int bi = ctx->bel_data(ci->bel).block_index; if (ctx->is_io_cell(ci->type)) write_io_cell(ci, loc.x, loc.y, bi); + else if (ci->type == id_MISTRAL_CLKENA) + write_clkbuf_cell(ci, loc.x, loc.y, bi); } } - void write_alm(uint32_t lab, uint8_t alm) + bool write_alm(uint32_t lab, uint8_t alm) { auto &alm_data = ctx->labs.at(lab).alms.at(alm); @@ -208,20 +218,23 @@ struct MistralBitgen // Skip empty ALMs if (std::all_of(luts.begin(), luts.end(), [](CellInfo *c) { return !c; }) && std::all_of(ffs.begin(), ffs.end(), [](CellInfo *c) { return !c; })) - return; + return false; auto pos = alm_data.lut_bels[0].pos; // Combinational mode - TODO: flop feedback cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::MODE, alm, alm_data.l6_mode ? CycloneV::L6 : CycloneV::L5); // LUT function cv->bmux_r_set(CycloneV::LAB, pos, CycloneV::LUT_MASK, alm, ctx->compute_lut_mask(lab, alm)); - // DFF output - foce to LUT for now... - cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::TDFF0, alm, CycloneV::NLUT); - cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::TDFF1, alm, CycloneV::NLUT); - cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::TDFF1L, alm, CycloneV::NLUT); - cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::BDFF0, alm, CycloneV::NLUT); - cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::BDFF1, alm, CycloneV::NLUT); - cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::BDFF1L, alm, CycloneV::NLUT); + // DFF/LUT output selection + const std::array mux_settings{CycloneV::TDFF0, CycloneV::TDFF1, CycloneV::TDFF1L, + CycloneV::BDFF0, CycloneV::BDFF1, CycloneV::BDFF1L}; + const std::array mux_port{CycloneV::FFT0, CycloneV::FFT1, CycloneV::FFT1L, + CycloneV::FFB0, CycloneV::FFB1, CycloneV::FFB1L}; + for (int i = 0; i < 6; i++) { + if (ctx->wires_connected(alm_data.comb_out[i / 3], ctx->get_port(CycloneV::LAB, CycloneV::pos2x(pos), + CycloneV::pos2y(pos), alm, mux_port[i]))) + cv->bmux_m_set(CycloneV::LAB, pos, mux_settings[i], alm, CycloneV::NLUT); + } bool is_carry = (luts[0] && luts[0]->combInfo.is_carry) || (luts[1] && luts[1]->combInfo.is_carry); if (is_carry) @@ -229,13 +242,92 @@ struct MistralBitgen // The carry in/out enable bits if (is_carry && alm == 0 && !luts[0]->combInfo.carry_start) cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::TTO_DIS, alm, true); + if (is_carry && alm == 5) + cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::BTO_DIS, alm, true); + // Flipflop configuration + const std::array pkreg{CycloneV::TPKREG0, CycloneV::TPKREG1, CycloneV::BPKREG0, + CycloneV::BPKREG1}; + const std::array clk_sel{CycloneV::TCLK_SEL, CycloneV::BCLK_SEL}, + clr_sel{CycloneV::TCLR_SEL, CycloneV::BCLR_SEL}, sclr_dis{CycloneV::TSCLR_DIS, CycloneV::BSCLR_DIS}, + sload_en{CycloneV::TSLOAD_EN, CycloneV::BSLOAD_EN}; + + const std::array clk_choice{CycloneV::CLK0, CycloneV::CLK1, CycloneV::CLK2}; + + const std::array clk_inv{CycloneV::CLK0_INV, CycloneV::CLK1_INV, CycloneV::CLK2_INV}, + en_en{CycloneV::EN0_EN, CycloneV::EN1_EN, CycloneV::EN2_EN}, + en_ninv{CycloneV::EN0_NINV, CycloneV::EN1_NINV, CycloneV::EN2_NINV}; + const std::array aclr_inv{CycloneV::ACLR0_INV, CycloneV::ACLR1_INV}; + + for (int i = 0; i < 4; i++) { + CellInfo *ff = ffs[i]; + if (!ff) + continue; + // PKREG (input selection) + if (ctx->wires_connected(alm_data.sel_ef[i / 2], alm_data.ff_in[i])) + cv->bmux_b_set(CycloneV::LAB, pos, pkreg[i], alm, true); + // Control set + // CLK+ENA + int ce_idx = alm_data.clk_ena_idx[i / 2]; + cv->bmux_m_set(CycloneV::LAB, pos, clk_sel[i / 2], alm, clk_choice[ce_idx]); + if (ff->ffInfo.ctrlset.clk.inverted) + cv->bmux_b_set(CycloneV::LAB, pos, clk_inv[ce_idx], 0, true); + if (get_net_or_empty(ff, id_ENA) != nullptr) { // not using ffInfo.ctrlset, this has a fake net always to + // ensure different constants don't collide + cv->bmux_b_set(CycloneV::LAB, pos, en_en[ce_idx], 0, true); + cv->bmux_b_set(CycloneV::LAB, pos, en_ninv[ce_idx], 0, !ff->ffInfo.ctrlset.ena.inverted); + } else { + cv->bmux_b_set(CycloneV::LAB, pos, en_en[ce_idx], 0, false); + } + // ACLR + int aclr_idx = alm_data.aclr_idx[i / 2]; + cv->bmux_b_set(CycloneV::LAB, pos, clr_sel[i / 2], alm, aclr_idx == 1); + if (ff->ffInfo.ctrlset.aclr.inverted) + cv->bmux_b_set(CycloneV::LAB, pos, aclr_inv[aclr_idx], 0, true); + // SCLR + if (ff->ffInfo.ctrlset.sclr.net != nullptr) { + cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::SCLR_INV, 0, ff->ffInfo.ctrlset.sclr.inverted); + } else { + cv->bmux_b_set(CycloneV::LAB, pos, sclr_dis[i / 2], alm, true); + } + // SLOAD + if (ff->ffInfo.ctrlset.sload.net != nullptr) { + cv->bmux_b_set(CycloneV::LAB, pos, sload_en[i / 2], alm, true); + cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::SLOAD_INV, 0, ff->ffInfo.ctrlset.sload.inverted); + } + } + return true; + } + + void write_ff_routing(uint32_t lab) + { + auto &lab_data = ctx->labs.at(lab); + auto pos = lab_data.alms.at(0).lut_bels[0].pos; + + const std::array aclr_inp{CycloneV::ACLR0_SEL, CycloneV::ACLR1_SEL}; + for (int i = 0; i < 2; i++) { + // Quartus seems to set unused ACLRs to CLKI2... + if (ctx->getBoundWireNet(lab_data.aclr_wires[i]) == nullptr) + cv->bmux_m_set(CycloneV::LAB, pos, aclr_inp[i], 0, CycloneV::CLKI2); + else + cv->bmux_m_set(CycloneV::LAB, pos, aclr_inp[i], 0, (i == 1) ? CycloneV::GIN0 : CycloneV::GIN1); + } + for (int i = 0; i < 3; i++) { + // Check for fabric->clock routing + if (ctx->wires_connected(ctx->get_port(CycloneV::LAB, CycloneV::pos2x(pos), CycloneV::pos2y(pos), -1, + CycloneV::DATAIN, 0), + lab_data.clk_wires[i])) + cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::CLKA_SEL, 0, CycloneV::GIN2); + } } void write_labs() { for (size_t lab = 0; lab < ctx->labs.size(); lab++) { + bool used = false; for (uint8_t alm = 0; alm < 10; alm++) - write_alm(lab, alm); + used |= write_alm(lab, alm); + if (used) + write_ff_routing(lab); } } diff --git a/mistral/lab.cc b/mistral/lab.cc index 3e6292b3..e117238b 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -614,33 +614,38 @@ void Arch::assign_control_sets(uint32_t lab) NPNR_ASSERT(legal); auto &lab_data = labs.at(lab); for (uint8_t alm = 0; alm < 10; alm++) { + auto &alm_data = lab_data.alms.at(alm); for (uint8_t i = 0; i < 4; i++) { - BelId ff_bel = lab_data.alms.at(alm).ff_bels.at(i); + BelId ff_bel = alm_data.ff_bels.at(i); const CellInfo *ff = getBoundBelCell(ff_bel); if (ff == nullptr) continue; ControlSig ena_sig = ff->ffInfo.ctrlset.ena; + WireId clk_wire = getBelPinWire(ff_bel, id_CLK); WireId ena_wire = getBelPinWire(ff_bel, id_ENA); - for (int i = 0; i < 3; i++) { - if (ena_sig == worker.datain[ena_datain[i]]) { + for (int j = 0; j < 3; j++) { + if (ena_sig == worker.datain[ena_datain[j]]) { if (getCtx()->debug) { log_info("Assigned CLK/ENA set %d to FF %s (%s)\n", i, nameOf(ff), getCtx()->nameOfBel(ff_bel)); } - reserve_route(lab_data.ena_wires[i], ena_wire); - // TODO: lock clock according to ENA choice, too + // TODO: lock clock according to ENA choice, too, when we support two clocks per ALM + reserve_route(lab_data.clk_wires[0], clk_wire); + reserve_route(lab_data.ena_wires[j], ena_wire); + alm_data.clk_ena_idx[i / 2] = j; break; } } ControlSig aclr_sig = ff->ffInfo.ctrlset.aclr; WireId aclr_wire = getBelPinWire(ff_bel, id_ACLR); - for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { // TODO: could be global ACLR, too - if (aclr_sig == worker.datain[aclr_datain[i]]) { + if (aclr_sig == worker.datain[aclr_datain[j]]) { if (getCtx()->debug) { log_info("Assigned ACLR set %d to FF %s (%s)\n", i, nameOf(ff), getCtx()->nameOfBel(ff_bel)); } - reserve_route(lab_data.aclr_wires[i], aclr_wire); + reserve_route(lab_data.aclr_wires[j], aclr_wire); + alm_data.aclr_idx[i / 2] = j; break; } } -- cgit v1.2.3 From b29fa1d24c44f7f6454158d27e751203c2d9e099 Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 14 May 2021 20:25:41 +0100 Subject: mistral: FF&CLKBUF fixes, part 1 Signed-off-by: gatecat --- mistral/bitstream.cc | 10 +++++++++- mistral/globals.cc | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index 7885f959..6921bbb5 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -189,7 +189,7 @@ struct MistralBitgen { (void)ci; // currently unused auto pos = CycloneV::xy2pos(x, y); - cv->bmux_n_set(CycloneV::CMUXHG, pos, CycloneV::INPUT_SELECT, bi, 0x1b); // hardcode to general routing + cv->bmux_r_set(CycloneV::CMUXHG, pos, CycloneV::INPUT_SELECT, bi, 0x1b); // hardcode to general routing cv->bmux_m_set(CycloneV::CMUXHG, pos, CycloneV::TESTSYN_ENOUT_SELECT, bi, CycloneV::PRE_SYNENB); } @@ -245,8 +245,10 @@ struct MistralBitgen if (is_carry && alm == 5) cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::BTO_DIS, alm, true); // Flipflop configuration + const std::array ef_sel{CycloneV::TEF_SEL, CycloneV::BEF_SEL}; const std::array pkreg{CycloneV::TPKREG0, CycloneV::TPKREG1, CycloneV::BPKREG0, CycloneV::BPKREG1}; + const std::array clk_sel{CycloneV::TCLK_SEL, CycloneV::BCLK_SEL}, clr_sel{CycloneV::TCLR_SEL, CycloneV::BCLR_SEL}, sclr_dis{CycloneV::TSCLR_DIS, CycloneV::BSCLR_DIS}, sload_en{CycloneV::TSLOAD_EN, CycloneV::BSLOAD_EN}; @@ -258,6 +260,12 @@ struct MistralBitgen en_ninv{CycloneV::EN0_NINV, CycloneV::EN1_NINV, CycloneV::EN2_NINV}; const std::array aclr_inv{CycloneV::ACLR0_INV, CycloneV::ACLR1_INV}; + for (int i = 0; i < 2; i++) { + // EF selection mux + if (ctx->wires_connected(ctx->getBelPinWire(alm_data.lut_bels[i], i ? id_F1 : id_F0), alm_data.sel_ef[i])) + cv->bmux_m_set(CycloneV::LAB, pos, ef_sel[i], alm, CycloneV::bmux_type_t::F); + } + for (int i = 0; i < 4; i++) { CellInfo *ff = ffs[i]; if (!ff) diff --git a/mistral/globals.cc b/mistral/globals.cc index 2d1b2ef8..eee8d0a9 100644 --- a/mistral/globals.cc +++ b/mistral/globals.cc @@ -32,6 +32,7 @@ void Arch::create_clkbuf(int x, int y) add_bel_pin(bel, id_A, PORT_IN, get_port(CycloneV::CMUXHG, x, y, -1, CycloneV::CLKIN, z)); add_bel_pin(bel, id_Q, PORT_OUT, get_port(CycloneV::CMUXHG, x, y, z, CycloneV::CLKOUT)); // TODO: enable pin + bel_data(bel).block_index = z; } } -- cgit v1.2.3 From dce847b2f3ab5257b41c2c12c2146ef400d56135 Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 14 May 2021 21:13:43 +0100 Subject: mistral: Trim SDATA if SLOAD is low Signed-off-by: gatecat --- mistral/pack.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mistral/pack.cc b/mistral/pack.cc index f8c55cd1..e704160c 100644 --- a/mistral/pack.cc +++ b/mistral/pack.cc @@ -167,6 +167,15 @@ struct MistralPacker if (ci->type != id_MISTRAL_NOT && ci->type != id_GND && ci->type != id_VCC) process_inv_constants(cell.second); } + // Special case - SDATA can only be trimmed if SLOAD is low + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_MISTRAL_FF) + continue; + if (ci->get_pin_state(id_SLOAD) != PIN_0) + continue; + disconnect_port(ctx, ci, id_SDATA); + } // Remove superfluous inverters and constant drivers trim_design(); } -- cgit v1.2.3 From 757a10c247615e85b6a04cb9524536857e54ced6 Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 14 May 2021 21:59:41 +0100 Subject: mistral: Debugging flipflops Signed-off-by: gatecat --- mistral/lab.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mistral/lab.cc b/mistral/lab.cc index e117238b..734592fd 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -384,7 +384,7 @@ bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const bool route_thru_lut_avail = !luts[i] && !carry_mode && (total_lut_inputs < 8) && (used_lut_bits < 64); // E/F is available if this LUT is using 3 or fewer inputs - this is conservative and sharing can probably // improve this situation - bool ef_available = (!luts[i] || luts[i]->combInfo.used_lut_input_count <= 3); + bool ef_available = (!luts[i] || (luts[i]->combInfo.used_lut_input_count <= 2)); // Control set checking bool found_ff = false; @@ -393,6 +393,8 @@ bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const const CellInfo *ff = ffs[i * 2 + j]; if (!ff) continue; + if (j == 1) + return false; // TODO: why are these FFs broken? if (found_ff) { // Two FFs in the same half with an incompatible control set if (ctrlset != ff->ffInfo.ctrlset) @@ -782,7 +784,7 @@ void Arch::reassign_alm_inputs(uint32_t lab, uint8_t alm) continue; for (int j = 0; j < 2; j++) { CellInfo *ff = ffs[i * 2 + j]; - if (!ff || !ff->ffInfo.datain) + if (!ff || !ff->ffInfo.datain || alm_data.l6_mode) continue; CellInfo *rt_lut = createCell(id(stringf("%s$ROUTETHRU", nameOf(ff))), id_MISTRAL_BUF); rt_lut->addInput(id_A); @@ -793,7 +795,6 @@ void Arch::reassign_alm_inputs(uint32_t lab, uint8_t alm) connect_port(getCtx(), datain, rt_lut, id_A); connect_ports(getCtx(), rt_lut, id_Q, ff, id_DATAIN); // Assign route-thru LUT physical ports, input goes to the first half-specific input - NPNR_ASSERT(!alm_data.l6_mode); rt_lut->pin_data[id_A].bel_pins.push_back(i ? id_D : id_C); rt_lut->pin_data[id_Q].bel_pins.push_back(id_COMBOUT); assign_comb_info(rt_lut); -- cgit v1.2.3 From 8bc9732d49fe645f29bc840c503ddb94a07f6e4c Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 14 May 2021 22:44:06 +0100 Subject: mistral: PKREG bits appear to be mirrored within a half? Signed-off-by: gatecat --- mistral/bitstream.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index 6921bbb5..8e78b228 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -246,8 +246,9 @@ struct MistralBitgen cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::BTO_DIS, alm, true); // Flipflop configuration const std::array ef_sel{CycloneV::TEF_SEL, CycloneV::BEF_SEL}; - const std::array pkreg{CycloneV::TPKREG0, CycloneV::TPKREG1, CycloneV::BPKREG0, - CycloneV::BPKREG1}; + // This isn't a typo; the *PKREG* bits really are mirrored. + const std::array pkreg{CycloneV::TPKREG1, CycloneV::TPKREG0, CycloneV::BPKREG1, + CycloneV::BPKREG0}; const std::array clk_sel{CycloneV::TCLK_SEL, CycloneV::BCLK_SEL}, clr_sel{CycloneV::TCLR_SEL, CycloneV::BCLR_SEL}, sclr_dis{CycloneV::TSCLR_DIS, CycloneV::BSCLR_DIS}, -- cgit v1.2.3 From 87ebada258dbb6aa4bdf5552ed7a3dc85fcdf17c Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 14 May 2021 23:08:32 +0100 Subject: mistral: Fix EF_SEL and BTO_DIS Signed-off-by: gatecat --- mistral/bitstream.cc | 6 +++--- mistral/lab.cc | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index 8e78b228..2c7f7862 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -241,9 +241,9 @@ struct MistralBitgen cv->bmux_m_set(CycloneV::LAB, pos, CycloneV::ARITH_SEL, alm, CycloneV::ADDER); // The carry in/out enable bits if (is_carry && alm == 0 && !luts[0]->combInfo.carry_start) - cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::TTO_DIS, alm, true); + cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::TTO_DIS, 0, true); if (is_carry && alm == 5) - cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::BTO_DIS, alm, true); + cv->bmux_b_set(CycloneV::LAB, pos, CycloneV::BTO_DIS, 0, true); // Flipflop configuration const std::array ef_sel{CycloneV::TEF_SEL, CycloneV::BEF_SEL}; // This isn't a typo; the *PKREG* bits really are mirrored. @@ -263,7 +263,7 @@ struct MistralBitgen for (int i = 0; i < 2; i++) { // EF selection mux - if (ctx->wires_connected(ctx->getBelPinWire(alm_data.lut_bels[i], i ? id_F1 : id_F0), alm_data.sel_ef[i])) + if (ctx->wires_connected(ctx->getBelPinWire(alm_data.lut_bels[i], i ? id_F0 : id_F1), alm_data.sel_ef[i])) cv->bmux_m_set(CycloneV::LAB, pos, ef_sel[i], alm, CycloneV::bmux_type_t::F); } diff --git a/mistral/lab.cc b/mistral/lab.cc index 734592fd..50ef7b11 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -104,8 +104,9 @@ static void create_alm(Arch *arch, int x, int y, int z, uint32_t lab_idx) arch->add_pip(lab.aclr_wires[j], alm.sel_aclr[i]); } // E/F pips + // Note that the F choice is mirrored, F from the other half is picked arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::E1 : CycloneV::E0), alm.sel_ef[i]); - arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::F1 : CycloneV::F0), alm.sel_ef[i]); + arch->add_pip(arch->get_port(CycloneV::LAB, x, y, z, i ? CycloneV::F0 : CycloneV::F1), alm.sel_ef[i]); } // Create the flipflops and associated routing -- cgit v1.2.3 From e1aaf715c6065c728e1dfe250ef419d1fc405553 Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 14 May 2021 23:13:30 +0100 Subject: mistral: Compensate for EF_SEL mirroring in validity check Signed-off-by: gatecat --- mistral/lab.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mistral/lab.cc b/mistral/lab.cc index 50ef7b11..838c72a3 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -384,8 +384,8 @@ bool Arch::is_alm_legal(uint32_t lab, uint8_t alm) const // signals and SLOAD=1 (*PKREF*) bool route_thru_lut_avail = !luts[i] && !carry_mode && (total_lut_inputs < 8) && (used_lut_bits < 64); // E/F is available if this LUT is using 3 or fewer inputs - this is conservative and sharing can probably - // improve this situation - bool ef_available = (!luts[i] || (luts[i]->combInfo.used_lut_input_count <= 2)); + // improve this situation. (1 - i) because the F input to EF_SEL is mirrored. + bool ef_available = (!luts[1 - i] || (luts[1 - i]->combInfo.used_lut_input_count <= 2)); // Control set checking bool found_ff = false; -- cgit v1.2.3 From 511e46c40fb99b8b708e58b70fd9f4642222a67e Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 15 May 2021 09:51:05 +0100 Subject: router2: Reduce verbosity when debugging Signed-off-by: gatecat --- common/router2.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/router2.cc b/common/router2.cc index 59763f86..cbed0f69 100644 --- a/common/router2.cc +++ b/common/router2.cc @@ -710,10 +710,12 @@ struct Router2 if (is_bb && !hit_test_pip(nd.bb, ctx->getPipLocation(dh))) continue; if (!ctx->checkPipAvailForNet(dh, net)) { +#if 0 ROUTE_LOG_DBG("Skipping pip %s because it is bound to net '%s' not net '%s'\n", ctx->nameOfPip(dh), ctx->getBoundPipNet(dh) != nullptr ? ctx->getBoundPipNet(dh)->name.c_str(ctx) : "", net->name.c_str(ctx)); +#endif continue; } #endif -- cgit v1.2.3 From 4d32c4f2fcb5c5e5ca21100f4473acb2c4cda3b0 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 15 May 2021 09:58:00 +0100 Subject: mistral: Disable global buffers that are currently broken Signed-off-by: gatecat --- mistral/globals.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mistral/globals.cc b/mistral/globals.cc index eee8d0a9..97e35518 100644 --- a/mistral/globals.cc +++ b/mistral/globals.cc @@ -26,6 +26,8 @@ NEXTPNR_NAMESPACE_BEGIN void Arch::create_clkbuf(int x, int y) { for (int z = 0; z < 4; z++) { + if (z != 2) + continue; // TODO: why do other Zs not work? // For now we only consider the input path from general routing, other inputs like dedicated clock pins are // still a TODO BelId bel = add_bel(x, y, id(stringf("CLKBUF[%d]", z)), id_MISTRAL_CLKENA); -- cgit v1.2.3 From 9221acc9e211766d79d7c7dde5d5fc8bb053354d Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 15 May 2021 10:26:27 +0100 Subject: mistral: Fix ENA and ACLR bitstream generation Signed-off-by: gatecat --- mistral/arch.h | 1 + mistral/bitstream.cc | 4 ++-- mistral/globals.cc | 4 ++-- mistral/lab.cc | 6 ++++++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/mistral/arch.h b/mistral/arch.h index 97d6a2b1..2360fb20 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -65,6 +65,7 @@ struct LABInfo std::array aclr_wires; WireId sclr_wire, sload_wire; // TODO: LAB configuration (control set etc) + std::array aclr_used; }; struct PinInfo diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index 2c7f7862..e31b94af 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -283,7 +283,7 @@ struct MistralBitgen if (get_net_or_empty(ff, id_ENA) != nullptr) { // not using ffInfo.ctrlset, this has a fake net always to // ensure different constants don't collide cv->bmux_b_set(CycloneV::LAB, pos, en_en[ce_idx], 0, true); - cv->bmux_b_set(CycloneV::LAB, pos, en_ninv[ce_idx], 0, !ff->ffInfo.ctrlset.ena.inverted); + cv->bmux_b_set(CycloneV::LAB, pos, en_ninv[ce_idx], 0, ff->ffInfo.ctrlset.ena.inverted); } else { cv->bmux_b_set(CycloneV::LAB, pos, en_en[ce_idx], 0, false); } @@ -315,7 +315,7 @@ struct MistralBitgen const std::array aclr_inp{CycloneV::ACLR0_SEL, CycloneV::ACLR1_SEL}; for (int i = 0; i < 2; i++) { // Quartus seems to set unused ACLRs to CLKI2... - if (ctx->getBoundWireNet(lab_data.aclr_wires[i]) == nullptr) + if (!lab_data.aclr_used[i]) cv->bmux_m_set(CycloneV::LAB, pos, aclr_inp[i], 0, CycloneV::CLKI2); else cv->bmux_m_set(CycloneV::LAB, pos, aclr_inp[i], 0, (i == 1) ? CycloneV::GIN0 : CycloneV::GIN1); diff --git a/mistral/globals.cc b/mistral/globals.cc index 97e35518..9cbabbca 100644 --- a/mistral/globals.cc +++ b/mistral/globals.cc @@ -26,8 +26,8 @@ NEXTPNR_NAMESPACE_BEGIN void Arch::create_clkbuf(int x, int y) { for (int z = 0; z < 4; z++) { - if (z != 2) - continue; // TODO: why do other Zs not work? + if (z != 2) + continue; // TODO: why do other Zs not work? // For now we only consider the input path from general routing, other inputs like dedicated clock pins are // still a TODO BelId bel = add_bel(x, y, id(stringf("CLKBUF[%d]", z)), id_MISTRAL_CLKENA); diff --git a/mistral/lab.cc b/mistral/lab.cc index 838c72a3..d34cc0ca 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -616,6 +616,11 @@ void Arch::assign_control_sets(uint32_t lab) bool legal = worker.run(this, lab); NPNR_ASSERT(legal); auto &lab_data = labs.at(lab); + + for (int j = 0; j < 2; j++) { + lab_data.aclr_used[j] = false; + } + for (uint8_t alm = 0; alm < 10; alm++) { auto &alm_data = lab_data.alms.at(alm); for (uint8_t i = 0; i < 4; i++) { @@ -648,6 +653,7 @@ void Arch::assign_control_sets(uint32_t lab) log_info("Assigned ACLR set %d to FF %s (%s)\n", i, nameOf(ff), getCtx()->nameOfBel(ff_bel)); } reserve_route(lab_data.aclr_wires[j], aclr_wire); + lab_data.aclr_used[j] = (aclr_sig.net != nullptr); alm_data.aclr_idx[i / 2] = j; break; } -- cgit v1.2.3 From 34677d38837af88729a217fea7617c892dfb5a95 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 15 May 2021 11:53:04 +0100 Subject: mistral: Workaround for weird SCLR issue Signed-off-by: gatecat --- mistral/lab.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mistral/lab.cc b/mistral/lab.cc index d34cc0ca..a6512c9a 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -322,6 +322,13 @@ void Arch::assign_ff_info(CellInfo *cell) const cell->ffInfo.ctrlset.aclr = get_ctrlsig(getCtx(), cell, id_ACLR); cell->ffInfo.ctrlset.sclr = get_ctrlsig(getCtx(), cell, id_SCLR); cell->ffInfo.ctrlset.sload = get_ctrlsig(getCtx(), cell, id_SLOAD); + // If SCLR is used, but SLOAD isn't, then it seems like we need to pretend as if SLOAD is connected GND (so set + // [BT]SLOAD_EN inside the ALMs, and clear SLOAD_INV) + if (cell->ffInfo.ctrlset.sclr.net != nullptr && cell->ffInfo.ctrlset.sload.net == nullptr) { + cell->ffInfo.ctrlset.sload.net = nets.at(id("$PACKER_GND_NET")).get(); + cell->ffInfo.ctrlset.sload.inverted = false; + } + cell->ffInfo.sdata = get_net_or_empty(cell, id_SDATA); cell->ffInfo.datain = get_net_or_empty(cell, id_DATAIN); } -- cgit v1.2.3 From 7fbfd98b8a1cd36d80fa9b7aa3e529c5a510f7e1 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 15 May 2021 12:14:00 +0100 Subject: mistral: Speed up bel binding and checking Signed-off-by: gatecat --- mistral/arch.h | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/mistral/arch.h b/mistral/arch.h index 2360fb20..1bb2395c 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -79,8 +79,11 @@ 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 + + CellInfo *bound = nullptr; + + // For cases where we need to determine an original block index, due to multiple bels at the same tile this + // might not be the same as the nextpnr z-coordinate int block_index; std::unordered_map pins; // Info for different kinds of bels @@ -334,14 +337,25 @@ struct Arch : BaseArch void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) override { - BaseArch::bindBel(bel, cell, strength); + auto &data = bel_data(bel); + NPNR_ASSERT(data.bound == nullptr); + data.bound = cell; + cell->bel = bel; + cell->belStrength = strength; update_bel(bel); } void unbindBel(BelId bel) override { - BaseArch::unbindBel(bel); + auto &data = bel_data(bel); + NPNR_ASSERT(data.bound != nullptr); + data.bound->bel = BelId(); + data.bound->belStrength = STRENGTH_NONE; + data.bound = nullptr; update_bel(bel); } + bool checkBelAvail(BelId bel) const override { return bel_data(bel).bound == nullptr; } + CellInfo *getBoundBelCell(BelId bel) const override { return bel_data(bel).bound; } + CellInfo *getConflictingBelCell(BelId bel) const override { return bel_data(bel).bound; } void update_bel(BelId bel); BelId bel_by_block_idx(int x, int y, IdString type, int block_index) const; -- cgit v1.2.3 From f31889847462509c450e30f739bb2f2e2109546a Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 15 May 2021 13:22:49 +0100 Subject: router2: Hacky workaround for slow Cyclone V convergence Signed-off-by: gatecat --- common/router2.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/router2.cc b/common/router2.cc index cbed0f69..d189cd68 100644 --- a/common/router2.cc +++ b/common/router2.cc @@ -835,9 +835,6 @@ struct Router2 // Ripup failed arcs to start with // Check if arc is already legally routed if (check_arc_routing(net, i, j)) { -#if 0 - ROUTE_LOG_DBG("Arc '%s' (user %zu, arc %zu) already routed skipping.\n", ctx->nameOf(net), i, j); -#endif continue; } @@ -904,10 +901,13 @@ struct Router2 ++net_data.fail_count; if ((net_data.fail_count % 3) == 0) { // Every three times a net fails to route, expand the bounding box to increase the search space +#ifndef ARCH_MISTRAL + // This patch seems to make thing worse for CycloneV, as it slows down the resolution of TD congestion, disable it net_data.bb.x0 = std::max(net_data.bb.x0 - 1, 0); net_data.bb.y0 = std::max(net_data.bb.y0 - 1, 0); net_data.bb.x1 = std::min(net_data.bb.x1 + 1, ctx->getGridDimX()); net_data.bb.y1 = std::min(net_data.bb.y1 + 1, ctx->getGridDimY()); +#endif } } } -- cgit v1.2.3 From b1e1492dac21f2c790540290e61fdd593126f50c Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 15 May 2021 13:58:03 +0100 Subject: mistral: Make router2 the default Signed-off-by: gatecat --- mistral/arch.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mistral/arch.cc b/mistral/arch.cc index 9c661439..dd5c3b9a 100644 --- a/mistral/arch.cc +++ b/mistral/arch.cc @@ -477,7 +477,7 @@ const std::vector Arch::availablePlacers = {"sa", #endif }; -const std::string Arch::defaultRouter = "router1"; +const std::string Arch::defaultRouter = "router2"; const std::vector Arch::availableRouters = {"router1", "router2"}; NEXTPNR_NAMESPACE_END \ No newline at end of file -- cgit v1.2.3 From 3bb94192d54bfcdbcd58dacd298aa6ff6d9b2bd2 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 15 May 2021 14:51:12 +0100 Subject: mistral: Tidying up Signed-off-by: gatecat --- common/archcheck.cc | 2 +- common/router2.cc | 3 ++- mistral/arch.cc | 2 +- mistral/arch.h | 2 +- mistral/archdefs.h | 2 +- mistral/base_bitstream.cc | 2 +- mistral/constids.inc | 2 +- mistral/io.cc | 2 +- mistral/lab.cc | 2 +- mistral/pack.cc | 2 +- mistral/pins.cc | 2 +- mistral/qsf.cc | 2 +- 12 files changed, 13 insertions(+), 12 deletions(-) diff --git a/common/archcheck.cc b/common/archcheck.cc index 80d8ef58..f46db95c 100644 --- a/common/archcheck.cc +++ b/common/archcheck.cc @@ -28,7 +28,7 @@ USING_NEXTPNR_NAMESPACE -#ifndef ARCH_CYCLONEV +#ifndef ARCH_MISTRAL // The LRU cache to reduce memory usage during the connectivity check relies on getPips() having some spacial locality, // which the current CycloneV arch impl doesn't have. This may be fixed in the future, though. #define USING_LRU_CACHE diff --git a/common/router2.cc b/common/router2.cc index d189cd68..ebd4e390 100644 --- a/common/router2.cc +++ b/common/router2.cc @@ -902,7 +902,8 @@ struct Router2 if ((net_data.fail_count % 3) == 0) { // Every three times a net fails to route, expand the bounding box to increase the search space #ifndef ARCH_MISTRAL - // This patch seems to make thing worse for CycloneV, as it slows down the resolution of TD congestion, disable it + // This patch seems to make thing worse for CycloneV, as it slows down the resolution of TD congestion, + // disable it net_data.bb.x0 = std::max(net_data.bb.x0 - 1, 0); net_data.bb.y0 = std::max(net_data.bb.y0 - 1, 0); net_data.bb.x1 = std::min(net_data.bb.x1 + 1, ctx->getGridDimX()); diff --git a/mistral/arch.cc b/mistral/arch.cc index dd5c3b9a..516d1de8 100644 --- a/mistral/arch.cc +++ b/mistral/arch.cc @@ -480,4 +480,4 @@ const std::vector Arch::availablePlacers = {"sa", const std::string Arch::defaultRouter = "router2"; const std::vector Arch::availableRouters = {"router1", "router2"}; -NEXTPNR_NAMESPACE_END \ No newline at end of file +NEXTPNR_NAMESPACE_END diff --git a/mistral/arch.h b/mistral/arch.h index 1bb2395c..c7d369f1 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -543,4 +543,4 @@ struct Arch : BaseArch NEXTPNR_NAMESPACE_END -#endif \ No newline at end of file +#endif diff --git a/mistral/archdefs.h b/mistral/archdefs.h index 181bc618..5fd02ad5 100644 --- a/mistral/archdefs.h +++ b/mistral/archdefs.h @@ -233,4 +233,4 @@ template <> struct hash } // namespace std -#endif \ No newline at end of file +#endif diff --git a/mistral/base_bitstream.cc b/mistral/base_bitstream.cc index 759cdfeb..9fa74fb9 100644 --- a/mistral/base_bitstream.cc +++ b/mistral/base_bitstream.cc @@ -97,4 +97,4 @@ void Arch::init_base_bitstream() } } -NEXTPNR_NAMESPACE_END \ No newline at end of file +NEXTPNR_NAMESPACE_END diff --git a/mistral/constids.inc b/mistral/constids.inc index 76d82a0b..4f7d3ecf 100644 --- a/mistral/constids.inc +++ b/mistral/constids.inc @@ -74,4 +74,4 @@ X(Y) X(LOC) -X(MISTRAL_CLKENA) \ No newline at end of file +X(MISTRAL_CLKENA) diff --git a/mistral/io.cc b/mistral/io.cc index 00918317..3a72b001 100644 --- a/mistral/io.cc +++ b/mistral/io.cc @@ -58,4 +58,4 @@ BelId Arch::get_io_pin_bel(const CycloneV::pin_info_t *pin) const return bel_by_block_idx(CycloneV::pos2x(pos), CycloneV::pos2y(pos), id_MISTRAL_IO, (pad >> 14)); } -NEXTPNR_NAMESPACE_END \ No newline at end of file +NEXTPNR_NAMESPACE_END diff --git a/mistral/lab.cc b/mistral/lab.cc index a6512c9a..abd0fec3 100644 --- a/mistral/lab.cc +++ b/mistral/lab.cc @@ -966,4 +966,4 @@ uint64_t Arch::compute_lut_mask(uint32_t lab, uint8_t alm) return mask; } -NEXTPNR_NAMESPACE_END \ No newline at end of file +NEXTPNR_NAMESPACE_END diff --git a/mistral/pack.cc b/mistral/pack.cc index e704160c..90fbfd78 100644 --- a/mistral/pack.cc +++ b/mistral/pack.cc @@ -362,4 +362,4 @@ bool Arch::pack() return true; } -NEXTPNR_NAMESPACE_END \ No newline at end of file +NEXTPNR_NAMESPACE_END diff --git a/mistral/pins.cc b/mistral/pins.cc index d0ce642e..c3637115 100644 --- a/mistral/pins.cc +++ b/mistral/pins.cc @@ -64,4 +64,4 @@ CellPinStyle Arch::get_cell_pin_style(const CellInfo *cell, IdString port) const return PINSTYLE_NONE; } -NEXTPNR_NAMESPACE_END \ No newline at end of file +NEXTPNR_NAMESPACE_END diff --git a/mistral/qsf.cc b/mistral/qsf.cc index 0c95bf2e..9a128595 100644 --- a/mistral/qsf.cc +++ b/mistral/qsf.cc @@ -278,4 +278,4 @@ void Arch::read_qsf(std::istream &in) QsfParser(buf, getCtx())(); } -NEXTPNR_NAMESPACE_END \ No newline at end of file +NEXTPNR_NAMESPACE_END -- cgit v1.2.3 From 6cef569155b5934893109d4ae509c94bdd1dc16a Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 15 May 2021 15:49:02 +0100 Subject: ci: Use GH only for Mistral and fpga-interchange Signed-off-by: gatecat --- .cirrus.yml | 3 +-- .github/ci/build_mistral.sh | 24 ++++++++++++++++++++++++ .github/workflows/mistral_ci.yml | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 .github/ci/build_mistral.sh create mode 100644 .github/workflows/mistral_ci.yml diff --git a/.cirrus.yml b/.cirrus.yml index 99c70ffd..86edee59 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -6,13 +6,12 @@ task: dockerfile: .cirrus/Dockerfile.ubuntu20.04 submodule_script: git submodule sync --recursive && git submodule update --init --recursive - build_script: mkdir build && cd build && cmake .. -DARCH=all+alpha -DOXIDE_INSTALL_PREFIX=$HOME/.cargo -DBUILD_TESTS=on -DBUILD_GUI=on -DWERROR=on && make -j3 + build_script: mkdir build && cd build && cmake .. -DARCH='ecp5;generic;gowin;ice40;machxo2;nexus' -DOXIDE_INSTALL_PREFIX=$HOME/.cargo -DBUILD_TESTS=on -DBUILD_GUI=on -DWERROR=on && make -j3 test_generic_script: cd build && ./nextpnr-generic-test flow_test_generic_script: export NPNR=$(pwd)/build/nextpnr-generic && cd tests/generic/flow && ./run.sh test_ice40_script: cd build && ./nextpnr-ice40-test smoketest_ice40_script: export NEXTPNR=$(pwd)/build/nextpnr-ice40 && cd ice40/smoketest/attosoc && ./smoketest.sh test_ecp5_script: cd build && ./nextpnr-ecp5-test - test_fpga_interchange_script: cd build && ./nextpnr-fpga_interchange-test smoketest_generic_script: export NEXTPNR=$(pwd)/build/nextpnr-generic && cd generic/examples && ./simple.sh && ./simtest.sh regressiontest_ice40_script: make -j $(nproc) -C tests/ice40/regressions NPNR=$(pwd)/build/nextpnr-ice40 regressiontest_ecp5_script: make -j $(nproc) -C tests/ecp5/regressions NPNR=$(pwd)/build/nextpnr-ecp5 diff --git a/.github/ci/build_mistral.sh b/.github/ci/build_mistral.sh new file mode 100644 index 00000000..7046462f --- /dev/null +++ b/.github/ci/build_mistral.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +function get_dependencies { + # Fetch mistral + mkdir -p ${MISTRAL_PATH} + git clone --recursive https://github.com/Ravenslofty/mistral.git ${MISTRAL_PATH} + pushd ${MISTRAL_PATH} + git reset --hard ${MISTRAL_REVISION} + popd +} + +function build_nextpnr { + mkdir build + pushd build + cmake .. -DARCH=mistral -DMISTRAL_ROOT=${MISTRAL_PATH} + make nextpnr-mistral -j`nproc` + popd +} + +function run_archcheck { + pushd build + ./nextpnr-mistral --mistral ${MISTRAL_PATH} --device 5CEBA2F17A7 --test + popd +} diff --git a/.github/workflows/mistral_ci.yml b/.github/workflows/mistral_ci.yml new file mode 100644 index 00000000..877e374d --- /dev/null +++ b/.github/workflows/mistral_ci.yml @@ -0,0 +1,33 @@ +name: Mistral CI tests + +on: [push, pull_request] + +jobs: + Build-nextpnr: + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v2 + with: + submodules: recursive + + - uses: actions/setup-python@v2 + + - name: Install + run: | + sudo apt-get update + sudo apt-get install git make cmake libboost-all-dev python3-dev libeigen3-dev tcl-dev lzma-dev clang bison flex swig + + - name: ccache + uses: hendrikmuhs/ccache-action@v1 + + - name: Execute build nextpnr + env: + MISTRAL_PATH: ${{ github.workspace }}/deps/mistral + MISTRAL_REVISION: 7d4e6d2cca1ec05de3be0c9fef6acaed8089d329 + run: | + export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" + source ./.github/ci/build_mistral.sh + get_dependencies + build_nextpnr + run_archcheck -- cgit v1.2.3 From 9d7f90dd89d98457e00579615ef9251d0f69f3a4 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 15 May 2021 21:28:48 +0100 Subject: mistral: Add MISTRAL_CLKBUF cell type Signed-off-by: gatecat --- mistral/arch.cc | 4 ++++ mistral/arch.h | 4 ++++ mistral/bitstream.cc | 2 +- mistral/constids.inc | 1 + mistral/globals.cc | 5 +++++ 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/mistral/arch.cc b/mistral/arch.cc index 516d1de8..cfa3e8b3 100644 --- a/mistral/arch.cc +++ b/mistral/arch.cc @@ -248,6 +248,8 @@ bool Arch::isValidBelForCellType(IdString cell_type, BelId bel) const return is_comb_cell(cell_type); else if (bel_type == id_MISTRAL_IO) return is_io_cell(cell_type); + else if (bel_type == id_MISTRAL_CLKENA) + return is_clkbuf_cell(cell_type); else return bel_type == cell_type; } @@ -258,6 +260,8 @@ BelBucketId Arch::getBelBucketForCellType(IdString cell_type) const return id_MISTRAL_COMB; else if (is_io_cell(cell_type)) return id_MISTRAL_IO; + else if (is_clkbuf_cell(cell_type)) + return id_MISTRAL_CLKENA; else return cell_type; } diff --git a/mistral/arch.h b/mistral/arch.h index c7d369f1..5913a615 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -480,6 +480,10 @@ struct Arch : BaseArch // ------------------------------------------------- + bool is_clkbuf_cell(IdString cell_type) const; // globals.cc + + // ------------------------------------------------- + static const std::string defaultPlacer; static const std::vector availablePlacers; static const std::string defaultRouter; diff --git a/mistral/bitstream.cc b/mistral/bitstream.cc index e31b94af..92d86410 100644 --- a/mistral/bitstream.cc +++ b/mistral/bitstream.cc @@ -201,7 +201,7 @@ struct MistralBitgen int bi = ctx->bel_data(ci->bel).block_index; if (ctx->is_io_cell(ci->type)) write_io_cell(ci, loc.x, loc.y, bi); - else if (ci->type == id_MISTRAL_CLKENA) + else if (ctx->is_clkbuf_cell(ci->type)) write_clkbuf_cell(ci, loc.x, loc.y, bi); } } diff --git a/mistral/constids.inc b/mistral/constids.inc index 4f7d3ecf..6bb45c3c 100644 --- a/mistral/constids.inc +++ b/mistral/constids.inc @@ -75,3 +75,4 @@ X(Y) X(LOC) X(MISTRAL_CLKENA) +X(MISTRAL_CLKBUF) diff --git a/mistral/globals.cc b/mistral/globals.cc index 9cbabbca..3203893b 100644 --- a/mistral/globals.cc +++ b/mistral/globals.cc @@ -38,4 +38,9 @@ void Arch::create_clkbuf(int x, int y) } } +bool Arch::is_clkbuf_cell(IdString cell_type) const +{ + return cell_type == id_MISTRAL_CLKENA || cell_type == id_MISTRAL_CLKBUF; +} + NEXTPNR_NAMESPACE_END -- cgit v1.2.3 From 3eeb2b20ebd3e527fb82a46774f4584575a3a9e1 Mon Sep 17 00:00:00 2001 From: gatecat Date: Sat, 15 May 2021 21:51:56 +0100 Subject: Update README Signed-off-by: gatecat --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8ebdec1a..1c0acb42 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Currently nextpnr supports: * Lattice ECP5 devices supported by [Project Trellis](https://github.com/YosysHQ/prjtrellis) * Lattice Nexus devices supported by [Project Oxide](https://github.com/gatecat/prjoxide) * Gowin LittleBee devices supported by [Project Apicula](https://github.com/YosysHQ/apicula) + * *(experimental)* Cyclone V devices supported by [Mistral](https://github.com/Ravenslofty/mistral) * *(experimental)* a "generic" back-end for user-defined architectures There is some work in progress towards [support for Xilinx devices](https://github.com/gatecat/nextpnr-xilinx/) but it is not upstream and not intended for end users at the present time. We hope to see more FPGA families supported in the future. We would love your help in developing this awesome new project! -- cgit v1.2.3