From 49f178ed94b5fad00d25dbd12adea0bf4732f803 Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 8 Apr 2022 13:42:54 +0100 Subject: Split up common into kernel,place,route Signed-off-by: gatecat --- CMakeLists.txt | 9 +- common/arch_api.h | 158 --- common/arch_pybindings_shared.h | 147 --- common/archcheck.cc | 408 ------ common/base_arch.h | 486 ------- common/base_clusterinfo.h | 45 - common/basectx.cc | 279 ---- common/basectx.h | 243 ---- common/bits.cc | 56 - common/bits.h | 76 -- common/chain_utils.h | 69 - common/command.cc | 563 -------- common/command.h | 74 -- common/constraints.h | 70 - common/constraints.impl.h | 109 -- common/context.cc | 428 ------- common/context.h | 119 -- common/design_utils.cc | 52 - common/design_utils.h | 100 -- common/deterministic_rng.h | 103 -- common/dynamic_bitarray.h | 211 --- common/embed.cc | 49 - common/embed.h | 49 - common/exclusive_state_groups.h | 154 --- common/exclusive_state_groups.impl.h | 89 -- common/fast_bels.h | 188 --- common/handle_error.cc | 61 - common/hashlib.h | 1210 ------------------ common/idstring.cc | 51 - common/idstring.h | 75 -- common/idstringlist.cc | 80 -- common/idstringlist.h | 87 -- common/indexed_store.h | 297 ----- common/kernel/arch_api.h | 158 +++ common/kernel/arch_pybindings_shared.h | 147 +++ common/kernel/archcheck.cc | 408 ++++++ common/kernel/base_arch.h | 486 +++++++ common/kernel/base_clusterinfo.h | 45 + common/kernel/basectx.cc | 279 ++++ common/kernel/basectx.h | 243 ++++ common/kernel/bits.cc | 56 + common/kernel/bits.h | 76 ++ common/kernel/chain_utils.h | 69 + common/kernel/command.cc | 563 ++++++++ common/kernel/command.h | 74 ++ common/kernel/constraints.h | 70 + common/kernel/constraints.impl.h | 109 ++ common/kernel/context.cc | 428 +++++++ common/kernel/context.h | 119 ++ common/kernel/design_utils.cc | 52 + common/kernel/design_utils.h | 100 ++ common/kernel/deterministic_rng.h | 103 ++ common/kernel/dynamic_bitarray.h | 211 +++ common/kernel/embed.cc | 49 + common/kernel/embed.h | 49 + common/kernel/exclusive_state_groups.h | 154 +++ common/kernel/exclusive_state_groups.impl.h | 89 ++ common/kernel/handle_error.cc | 61 + common/kernel/hashlib.h | 1210 ++++++++++++++++++ common/kernel/idstring.cc | 51 + common/kernel/idstring.h | 75 ++ common/kernel/idstringlist.cc | 80 ++ common/kernel/idstringlist.h | 87 ++ common/kernel/indexed_store.h | 297 +++++ common/kernel/log.cc | 198 +++ common/kernel/log.h | 92 ++ common/kernel/nextpnr.cc | 35 + common/kernel/nextpnr.h | 29 + common/kernel/nextpnr_assertions.cc | 33 + common/kernel/nextpnr_assertions.h | 64 + common/kernel/nextpnr_base_types.h | 135 ++ common/kernel/nextpnr_namespaces.cc | 23 + common/kernel/nextpnr_namespaces.h | 58 + common/kernel/nextpnr_types.cc | 180 +++ common/kernel/nextpnr_types.h | 364 ++++++ common/kernel/property.cc | 80 ++ common/kernel/property.h | 131 ++ common/kernel/pybindings.cc | 362 ++++++ common/kernel/pybindings.h | 93 ++ common/kernel/pycontainers.h | 575 +++++++++ common/kernel/pywrappers.h | 463 +++++++ common/kernel/relptr.h | 74 ++ common/kernel/report.cc | 259 ++++ common/kernel/scope_lock.h | 67 + common/kernel/sdf.cc | 334 +++++ common/kernel/sso_array.h | 132 ++ common/kernel/str_ring_buffer.cc | 34 + common/kernel/str_ring_buffer.h | 45 + common/kernel/svg.cc | 152 +++ common/kernel/timing.cc | 1515 ++++++++++++++++++++++ common/kernel/timing.h | 236 ++++ common/kernel/util.h | 241 ++++ common/log.cc | 198 --- common/log.h | 92 -- common/nextpnr.cc | 35 - common/nextpnr.h | 29 - common/nextpnr_assertions.cc | 33 - common/nextpnr_assertions.h | 64 - common/nextpnr_base_types.h | 135 -- common/nextpnr_namespaces.cc | 23 - common/nextpnr_namespaces.h | 58 - common/nextpnr_types.cc | 180 --- common/nextpnr_types.h | 364 ------ common/parallel_refine.cc | 959 -------------- common/parallel_refine.h | 43 - common/place/fast_bels.h | 188 +++ common/place/parallel_refine.cc | 959 ++++++++++++++ common/place/parallel_refine.h | 43 + common/place/place_common.cc | 487 +++++++ common/place/place_common.h | 55 + common/place/placer1.cc | 1317 +++++++++++++++++++ common/place/placer1.h | 45 + common/place/placer_heap.cc | 1830 +++++++++++++++++++++++++++ common/place/placer_heap.h | 58 + common/place/timing_opt.cc | 561 ++++++++ common/place/timing_opt.h | 37 + common/place_common.cc | 487 ------- common/place_common.h | 55 - common/placer1.cc | 1317 ------------------- common/placer1.h | 45 - common/placer_heap.cc | 1830 --------------------------- common/placer_heap.h | 58 - common/property.cc | 80 -- common/property.h | 131 -- common/pybindings.cc | 362 ------ common/pybindings.h | 93 -- common/pycontainers.h | 575 --------- common/pywrappers.h | 463 ------- common/relptr.h | 74 -- common/report.cc | 259 ---- common/route/router1.cc | 1175 +++++++++++++++++ common/route/router1.h | 45 + common/route/router2.cc | 1499 ++++++++++++++++++++++ common/route/router2.h | 66 + common/router1.cc | 1175 ----------------- common/router1.h | 45 - common/router2.cc | 1499 ---------------------- common/router2.h | 66 - common/scope_lock.h | 67 - common/sdf.cc | 334 ----- common/sso_array.h | 132 -- common/str_ring_buffer.cc | 34 - common/str_ring_buffer.h | 45 - common/svg.cc | 152 --- common/timing.cc | 1515 ---------------------- common/timing.h | 236 ---- common/timing_opt.cc | 561 -------- common/timing_opt.h | 37 - common/util.h | 241 ---- 149 files changed, 20373 insertions(+), 20370 deletions(-) delete mode 100644 common/arch_api.h delete mode 100644 common/arch_pybindings_shared.h delete mode 100644 common/archcheck.cc delete mode 100644 common/base_arch.h delete mode 100644 common/base_clusterinfo.h delete mode 100644 common/basectx.cc delete mode 100644 common/basectx.h delete mode 100644 common/bits.cc delete mode 100644 common/bits.h delete mode 100644 common/chain_utils.h delete mode 100644 common/command.cc delete mode 100644 common/command.h delete mode 100644 common/constraints.h delete mode 100644 common/constraints.impl.h delete mode 100644 common/context.cc delete mode 100644 common/context.h delete mode 100644 common/design_utils.cc delete mode 100644 common/design_utils.h delete mode 100644 common/deterministic_rng.h delete mode 100644 common/dynamic_bitarray.h delete mode 100644 common/embed.cc delete mode 100644 common/embed.h delete mode 100644 common/exclusive_state_groups.h delete mode 100644 common/exclusive_state_groups.impl.h delete mode 100644 common/fast_bels.h delete mode 100644 common/handle_error.cc delete mode 100644 common/hashlib.h delete mode 100644 common/idstring.cc delete mode 100644 common/idstring.h delete mode 100644 common/idstringlist.cc delete mode 100644 common/idstringlist.h delete mode 100644 common/indexed_store.h create mode 100644 common/kernel/arch_api.h create mode 100644 common/kernel/arch_pybindings_shared.h create mode 100644 common/kernel/archcheck.cc create mode 100644 common/kernel/base_arch.h create mode 100644 common/kernel/base_clusterinfo.h create mode 100644 common/kernel/basectx.cc create mode 100644 common/kernel/basectx.h create mode 100644 common/kernel/bits.cc create mode 100644 common/kernel/bits.h create mode 100644 common/kernel/chain_utils.h create mode 100644 common/kernel/command.cc create mode 100644 common/kernel/command.h create mode 100644 common/kernel/constraints.h create mode 100644 common/kernel/constraints.impl.h create mode 100644 common/kernel/context.cc create mode 100644 common/kernel/context.h create mode 100644 common/kernel/design_utils.cc create mode 100644 common/kernel/design_utils.h create mode 100644 common/kernel/deterministic_rng.h create mode 100644 common/kernel/dynamic_bitarray.h create mode 100644 common/kernel/embed.cc create mode 100644 common/kernel/embed.h create mode 100644 common/kernel/exclusive_state_groups.h create mode 100644 common/kernel/exclusive_state_groups.impl.h create mode 100644 common/kernel/handle_error.cc create mode 100644 common/kernel/hashlib.h create mode 100644 common/kernel/idstring.cc create mode 100644 common/kernel/idstring.h create mode 100644 common/kernel/idstringlist.cc create mode 100644 common/kernel/idstringlist.h create mode 100644 common/kernel/indexed_store.h create mode 100644 common/kernel/log.cc create mode 100644 common/kernel/log.h create mode 100644 common/kernel/nextpnr.cc create mode 100644 common/kernel/nextpnr.h create mode 100644 common/kernel/nextpnr_assertions.cc create mode 100644 common/kernel/nextpnr_assertions.h create mode 100644 common/kernel/nextpnr_base_types.h create mode 100644 common/kernel/nextpnr_namespaces.cc create mode 100644 common/kernel/nextpnr_namespaces.h create mode 100644 common/kernel/nextpnr_types.cc create mode 100644 common/kernel/nextpnr_types.h create mode 100644 common/kernel/property.cc create mode 100644 common/kernel/property.h create mode 100644 common/kernel/pybindings.cc create mode 100644 common/kernel/pybindings.h create mode 100644 common/kernel/pycontainers.h create mode 100644 common/kernel/pywrappers.h create mode 100644 common/kernel/relptr.h create mode 100644 common/kernel/report.cc create mode 100644 common/kernel/scope_lock.h create mode 100644 common/kernel/sdf.cc create mode 100644 common/kernel/sso_array.h create mode 100644 common/kernel/str_ring_buffer.cc create mode 100644 common/kernel/str_ring_buffer.h create mode 100644 common/kernel/svg.cc create mode 100644 common/kernel/timing.cc create mode 100644 common/kernel/timing.h create mode 100644 common/kernel/util.h delete mode 100644 common/log.cc delete mode 100644 common/log.h delete mode 100644 common/nextpnr.cc delete mode 100644 common/nextpnr.h delete mode 100644 common/nextpnr_assertions.cc delete mode 100644 common/nextpnr_assertions.h delete mode 100644 common/nextpnr_base_types.h delete mode 100644 common/nextpnr_namespaces.cc delete mode 100644 common/nextpnr_namespaces.h delete mode 100644 common/nextpnr_types.cc delete mode 100644 common/nextpnr_types.h delete mode 100644 common/parallel_refine.cc delete mode 100644 common/parallel_refine.h create mode 100644 common/place/fast_bels.h create mode 100644 common/place/parallel_refine.cc create mode 100644 common/place/parallel_refine.h create mode 100644 common/place/place_common.cc create mode 100644 common/place/place_common.h create mode 100644 common/place/placer1.cc create mode 100644 common/place/placer1.h create mode 100644 common/place/placer_heap.cc create mode 100644 common/place/placer_heap.h create mode 100644 common/place/timing_opt.cc create mode 100644 common/place/timing_opt.h delete mode 100644 common/place_common.cc delete mode 100644 common/place_common.h delete mode 100644 common/placer1.cc delete mode 100644 common/placer1.h delete mode 100644 common/placer_heap.cc delete mode 100644 common/placer_heap.h delete mode 100644 common/property.cc delete mode 100644 common/property.h delete mode 100644 common/pybindings.cc delete mode 100644 common/pybindings.h delete mode 100644 common/pycontainers.h delete mode 100644 common/pywrappers.h delete mode 100644 common/relptr.h delete mode 100644 common/report.cc create mode 100644 common/route/router1.cc create mode 100644 common/route/router1.h create mode 100644 common/route/router2.cc create mode 100644 common/route/router2.h delete mode 100644 common/router1.cc delete mode 100644 common/router1.h delete mode 100644 common/router2.cc delete mode 100644 common/router2.h delete mode 100644 common/scope_lock.h delete mode 100644 common/sdf.cc delete mode 100644 common/sso_array.h delete mode 100644 common/str_ring_buffer.cc delete mode 100644 common/str_ring_buffer.h delete mode 100644 common/svg.cc delete mode 100644 common/timing.cc delete mode 100644 common/timing.h delete mode 100644 common/timing_opt.cc delete mode 100644 common/timing_opt.h delete mode 100644 common/util.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fcbbbb13..89bdb360 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -216,7 +216,7 @@ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/common/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/generated/version.h ) -include_directories(common/ json/ frontend/ 3rdparty/json11/ 3rdparty/pybind11/include ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) +include_directories(common/kernel/ common/place/ common/route/ json/ frontend/ 3rdparty/json11/ 3rdparty/pybind11/include ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) if(BUILD_HEAP) find_package (Eigen3 REQUIRED NO_MODULE) @@ -225,12 +225,15 @@ if(BUILD_HEAP) add_definitions(-DWITH_HEAP) endif() -aux_source_directory(common/ COMMON_SRC_FILES) +aux_source_directory(common/kernel/ KERNEL_SRC_FILES) +aux_source_directory(common/place/ PLACE_SRC_FILES) +aux_source_directory(common/route/ ROUTE_SRC_FILES) + aux_source_directory(json/ JSON_PARSER_FILES) aux_source_directory(3rdparty/json11 EXT_JSON11_FILES) aux_source_directory(frontend/ FRONTEND_FILES) -set(COMMON_FILES ${COMMON_SRC_FILES} ${EXT_JSON11_FILES} ${JSON_PARSER_FILES} ${FRONTEND_FILES}) +set(COMMON_FILES ${KERNEL_SRC_FILES} ${PLACE_SRC_FILES} ${ROUTE_SRC_FILES} ${EXT_JSON11_FILES} ${JSON_PARSER_FILES} ${FRONTEND_FILES}) if( NOT CMAKE_BUILD_TYPE ) set(CMAKE_BUILD_TYPE Release) endif() diff --git a/common/arch_api.h b/common/arch_api.h deleted file mode 100644 index 14a30652..00000000 --- a/common/arch_api.h +++ /dev/null @@ -1,158 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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_API_H -#define ARCH_API_H - -#include - -#include "basectx.h" -#include "idstring.h" -#include "idstringlist.h" -#include "nextpnr_assertions.h" -#include "nextpnr_namespaces.h" -#include "nextpnr_types.h" - -NEXTPNR_NAMESPACE_BEGIN - -// The specification of the Arch API (pure virtual) -template struct ArchAPI : BaseCtx -{ - // Basic config - virtual IdString archId() const = 0; - virtual std::string getChipName() const = 0; - virtual typename R::ArchArgsT archArgs() const = 0; - virtual IdString archArgsToId(typename R::ArchArgsT args) const = 0; - virtual int getGridDimX() const = 0; - virtual int getGridDimY() const = 0; - virtual int getTileBelDimZ(int x, int y) const = 0; - virtual int getTilePipDimZ(int x, int y) const = 0; - virtual char getNameDelimiter() const = 0; - // Bel methods - virtual typename R::AllBelsRangeT getBels() const = 0; - virtual IdStringList getBelName(BelId bel) const = 0; - virtual BelId getBelByName(IdStringList name) const = 0; - virtual uint32_t getBelChecksum(BelId bel) const = 0; - virtual void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) = 0; - virtual void unbindBel(BelId bel) = 0; - virtual Loc getBelLocation(BelId bel) const = 0; - virtual BelId getBelByLocation(Loc loc) const = 0; - virtual typename R::TileBelsRangeT getBelsByTile(int x, int y) const = 0; - virtual bool getBelGlobalBuf(BelId bel) const = 0; - virtual bool checkBelAvail(BelId bel) const = 0; - virtual CellInfo *getBoundBelCell(BelId bel) const = 0; - virtual CellInfo *getConflictingBelCell(BelId bel) const = 0; - virtual IdString getBelType(BelId bel) const = 0; - virtual bool getBelHidden(BelId bel) const = 0; - virtual typename R::BelAttrsRangeT getBelAttrs(BelId bel) const = 0; - virtual WireId getBelPinWire(BelId bel, IdString pin) const = 0; - virtual PortType getBelPinType(BelId bel, IdString pin) const = 0; - virtual typename R::BelPinsRangeT getBelPins(BelId bel) const = 0; - virtual typename R::CellBelPinRangeT getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const = 0; - // Wire methods - virtual typename R::AllWiresRangeT getWires() const = 0; - virtual WireId getWireByName(IdStringList name) const = 0; - virtual IdStringList getWireName(WireId wire) const = 0; - virtual IdString getWireType(WireId wire) const = 0; - virtual typename R::WireAttrsRangeT getWireAttrs(WireId) const = 0; - virtual typename R::DownhillPipRangeT getPipsDownhill(WireId wire) const = 0; - virtual typename R::UphillPipRangeT getPipsUphill(WireId wire) const = 0; - virtual typename R::WireBelPinRangeT getWireBelPins(WireId wire) const = 0; - virtual uint32_t getWireChecksum(WireId wire) const = 0; - virtual void bindWire(WireId wire, NetInfo *net, PlaceStrength strength) = 0; - virtual void unbindWire(WireId wire) = 0; - virtual bool checkWireAvail(WireId wire) const = 0; - virtual NetInfo *getBoundWireNet(WireId wire) const = 0; - virtual WireId getConflictingWireWire(WireId wire) const = 0; - virtual NetInfo *getConflictingWireNet(WireId wire) const = 0; - virtual DelayQuad getWireDelay(WireId wire) const = 0; - // Pip methods - virtual typename R::AllPipsRangeT getPips() const = 0; - virtual PipId getPipByName(IdStringList name) const = 0; - virtual IdStringList getPipName(PipId pip) const = 0; - virtual IdString getPipType(PipId pip) const = 0; - virtual typename R::PipAttrsRangeT getPipAttrs(PipId) const = 0; - virtual uint32_t getPipChecksum(PipId pip) const = 0; - virtual void bindPip(PipId pip, NetInfo *net, PlaceStrength strength) = 0; - virtual void unbindPip(PipId pip) = 0; - virtual bool checkPipAvail(PipId pip) const = 0; - virtual bool checkPipAvailForNet(PipId pip, NetInfo *net) const = 0; - virtual NetInfo *getBoundPipNet(PipId pip) const = 0; - virtual WireId getConflictingPipWire(PipId pip) const = 0; - virtual NetInfo *getConflictingPipNet(PipId pip) const = 0; - virtual WireId getPipSrcWire(PipId pip) const = 0; - virtual WireId getPipDstWire(PipId pip) const = 0; - virtual DelayQuad getPipDelay(PipId pip) const = 0; - virtual Loc getPipLocation(PipId pip) const = 0; - // Group methods - virtual GroupId getGroupByName(IdStringList name) const = 0; - virtual IdStringList getGroupName(GroupId group) const = 0; - virtual typename R::AllGroupsRangeT getGroups() const = 0; - virtual typename R::GroupBelsRangeT getGroupBels(GroupId group) const = 0; - virtual typename R::GroupWiresRangeT getGroupWires(GroupId group) const = 0; - virtual typename R::GroupPipsRangeT getGroupPips(GroupId group) const = 0; - virtual typename R::GroupGroupsRangeT getGroupGroups(GroupId group) const = 0; - // Delay Methods - virtual delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const = 0; - virtual delay_t getDelayEpsilon() const = 0; - virtual delay_t getRipupDelayPenalty() const = 0; - virtual float getDelayNS(delay_t v) const = 0; - virtual delay_t getDelayFromNS(float ns) const = 0; - virtual uint32_t getDelayChecksum(delay_t v) const = 0; - virtual bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const = 0; - virtual delay_t estimateDelay(WireId src, WireId dst) const = 0; - virtual ArcBounds getRouteBoundingBox(WireId src, WireId dst) const = 0; - // Decal methods - virtual typename R::DecalGfxRangeT getDecalGraphics(DecalId decal) const = 0; - virtual DecalXY getBelDecal(BelId bel) const = 0; - virtual DecalXY getWireDecal(WireId wire) const = 0; - virtual DecalXY getPipDecal(PipId pip) const = 0; - virtual DecalXY getGroupDecal(GroupId group) const = 0; - // Cell timing methods - virtual bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const = 0; - virtual TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const = 0; - virtual TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const = 0; - // Placement validity checks - virtual bool isValidBelForCellType(IdString cell_type, BelId bel) const = 0; - virtual IdString getBelBucketName(BelBucketId bucket) const = 0; - virtual BelBucketId getBelBucketByName(IdString name) const = 0; - virtual BelBucketId getBelBucketForBel(BelId bel) const = 0; - virtual BelBucketId getBelBucketForCellType(IdString cell_type) const = 0; - virtual bool isBelLocationValid(BelId bel) const = 0; - virtual typename R::CellTypeRangeT getCellTypes() const = 0; - virtual typename R::BelBucketRangeT getBelBuckets() const = 0; - virtual typename R::BucketBelRangeT getBelsInBucket(BelBucketId bucket) const = 0; - // Cluster methods - virtual CellInfo *getClusterRootCell(ClusterId cluster) const = 0; - virtual ArcBounds getClusterBounds(ClusterId cluster) const = 0; - virtual Loc getClusterOffset(const CellInfo *cell) const = 0; - virtual bool isClusterStrict(const CellInfo *cell) const = 0; - virtual bool getClusterPlacement(ClusterId cluster, BelId root_bel, - std::vector> &placement) const = 0; - // Flow methods - virtual bool pack() = 0; - virtual bool place() = 0; - virtual bool route() = 0; - virtual void assignArchInfo() = 0; -}; - -NEXTPNR_NAMESPACE_END - -#endif /* ARCH_API_H */ diff --git a/common/arch_pybindings_shared.h b/common/arch_pybindings_shared.h deleted file mode 100644 index b3dc0506..00000000 --- a/common/arch_pybindings_shared.h +++ /dev/null @@ -1,147 +0,0 @@ -// Common Python bindings #included by all arches - -readonly_wrapper>::def_wrap(ctx_cls, - "cells"); -readonly_wrapper>::def_wrap(ctx_cls, "nets"); -readonly_wrapper>::def_wrap( - ctx_cls, "net_aliases"); -readonly_wrapper>::def_wrap( - ctx_cls, "hierarchy"); -readwrite_wrapper, - conv_from_str>::def_wrap(ctx_cls, "top_module"); -readonly_wrapper>::def_wrap(ctx_cls, "timing_result"); - -fn_wrapper_0a>::def_wrap( - ctx_cls, "getNameDelimiter"); - -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getNetByAlias"); -fn_wrapper_2a_v, - pass_through>::def_wrap(ctx_cls, "addClock"); -fn_wrapper_5a_v, pass_through, pass_through, pass_through, - pass_through>::def_wrap(ctx_cls, "createRectangularRegion"); -fn_wrapper_2a_v, - conv_from_str>::def_wrap(ctx_cls, "addBelToRegion"); -fn_wrapper_2a_v, conv_from_str>::def_wrap(ctx_cls, "constrainCellToRegion"); - -fn_wrapper_2a, - addr_and_unwrap, unwrap_context>::def_wrap(ctx_cls, "getNetinfoRouteDelay"); - -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "createNet"); -fn_wrapper_3a_v, - conv_from_str, conv_from_str>::def_wrap(ctx_cls, "connectPort"); -fn_wrapper_2a_v, - conv_from_str>::def_wrap(ctx_cls, "disconnectPort"); -fn_wrapper_1a_v>::def_wrap( - ctx_cls, "ripupNet"); -fn_wrapper_1a_v>::def_wrap(ctx_cls, "lockNetRouting"); - -fn_wrapper_2a, - conv_from_str, conv_from_str>::def_wrap(ctx_cls, "createCell"); -fn_wrapper_2a_v, - conv_from_str>::def_wrap(ctx_cls, "copyBelPorts"); - -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getBelType"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getBelLocation"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "checkBelAvail"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getBelChecksum"); -fn_wrapper_3a_v, - addr_and_unwrap, pass_through>::def_wrap(ctx_cls, "bindBel"); -fn_wrapper_1a_v>::def_wrap( - ctx_cls, "unbindBel"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getBoundBelCell"); -fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getConflictingBelCell"); -fn_wrapper_0a>::def_wrap(ctx_cls, - "getBels"); - -fn_wrapper_2a, - conv_from_str, conv_from_str>::def_wrap(ctx_cls, "getBelPinWire"); -fn_wrapper_2a, - conv_from_str, conv_from_str>::def_wrap(ctx_cls, "getBelPinType"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getWireBelPins"); - -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getWireChecksum"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getWireType"); -fn_wrapper_3a_v, - addr_and_unwrap, pass_through>::def_wrap(ctx_cls, "bindWire"); -fn_wrapper_1a_v>::def_wrap( - ctx_cls, "unbindWire"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "checkWireAvail"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getBoundWireNet"); -fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getConflictingWireNet"); - -fn_wrapper_0a>::def_wrap(ctx_cls, - "getWires"); - -fn_wrapper_0a>::def_wrap(ctx_cls, - "getPips"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getPipChecksum"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getPipLocation"); -fn_wrapper_3a_v, addr_and_unwrap, - pass_through>::def_wrap(ctx_cls, "bindPip"); -fn_wrapper_1a_v>::def_wrap( - ctx_cls, "unbindPip"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "checkPipAvail"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getBoundPipNet"); -fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getConflictingPipNet"); - -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getPipsDownhill"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getPipsUphill"); - -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getPipSrcWire"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getPipDstWire"); -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getPipDelay"); - -fn_wrapper_0a>::def_wrap( - ctx_cls, "getChipName"); -fn_wrapper_0a>::def_wrap(ctx_cls, - "archId"); - -fn_wrapper_2a_v, - pass_through>::def_wrap(ctx_cls, "writeSVG"); - -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "isBelLocationValid"); - -// const\_range\ getBelBuckets() const -fn_wrapper_0a>::def_wrap(ctx_cls, "getBelBuckets"); -// BelBucketId getBelBucketForBel(BelId bel) const -fn_wrapper_1a, - conv_from_str>::def_wrap(ctx_cls, "getBelBucketForBel"); -// BelBucketId getBelBucketForCellType(IdString cell\_type) const -fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getBelBucketForCellType"); -// const\_range\ getBelsInBucket(BelBucketId bucket) const -fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getBelsInBucket"); -// bool isValidBelForCellType(IdString cell\_type, BelId bel) const -fn_wrapper_2a, - conv_from_str, conv_from_str>::def_wrap(ctx_cls, "isValidBelForCellType"); diff --git a/common/archcheck.cc b/common/archcheck.cc deleted file mode 100644 index 23ec7aee..00000000 --- a/common/archcheck.cc +++ /dev/null @@ -1,408 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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" - -#if 0 -#define dbg(...) log(__VA_ARGS__) -#else -#define dbg(...) -#endif - -USING_NEXTPNR_NAMESPACE - -#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 -#endif - -namespace { - -void archcheck_names(const Context *ctx) -{ - log_info("Checking entity names.\n"); - - log_info("Checking bel names..\n"); - for (BelId bel : ctx->getBels()) { - IdStringList name = ctx->getBelName(bel); - BelId bel2 = ctx->getBelByName(name); - if (bel != bel2) { - log_error("bel != bel2, name = %s\n", ctx->nameOfBel(bel)); - } - } - - log_info("Checking wire names..\n"); - for (WireId wire : ctx->getWires()) { - IdStringList name = ctx->getWireName(wire); - WireId wire2 = ctx->getWireByName(name); - if (wire != wire2) { - log_error("wire != wire2, name = %s\n", ctx->nameOfWire(wire)); - } - } - - log_info("Checking bucket names..\n"); - for (BelBucketId bucket : ctx->getBelBuckets()) { - IdString name = ctx->getBelBucketName(bucket); - BelBucketId bucket2 = ctx->getBelBucketByName(name); - if (bucket != bucket2) { - log_error("bucket != bucket2, name = %s\n", name.c_str(ctx)); - } - } - -#ifndef ARCH_ECP5 - log_info("Checking pip names..\n"); - for (PipId pip : ctx->getPips()) { - IdStringList name = ctx->getPipName(pip); - PipId pip2 = ctx->getPipByName(name); - if (pip != pip2) { - log_error("pip != pip2, name = %s\n", ctx->nameOfPip(pip)); - } - } -#endif - log_break(); -} - -void archcheck_locs(const Context *ctx) -{ - log_info("Checking location data.\n"); - - log_info("Checking all bels..\n"); - for (BelId bel : ctx->getBels()) { - log_assert(bel != BelId()); - dbg("> %s\n", ctx->getBelName(bel).c_str(ctx)); - - Loc loc = ctx->getBelLocation(bel); - dbg(" ... %d %d %d\n", loc.x, loc.y, loc.z); - - log_assert(0 <= loc.x); - log_assert(0 <= loc.y); - log_assert(0 <= loc.z); - log_assert(loc.x < ctx->getGridDimX()); - log_assert(loc.y < ctx->getGridDimY()); - log_assert(loc.z < ctx->getTileBelDimZ(loc.x, loc.y)); - - BelId bel2 = ctx->getBelByLocation(loc); - dbg(" ... %s\n", ctx->getBelName(bel2).c_str(ctx)); - log_assert(bel == bel2); - } - - log_info("Checking all locations..\n"); - for (int x = 0; x < ctx->getGridDimX(); x++) - for (int y = 0; y < ctx->getGridDimY(); y++) { - dbg("> %d %d\n", x, y); - pool usedz; - - for (int z = 0; z < ctx->getTileBelDimZ(x, y); z++) { - BelId bel = ctx->getBelByLocation(Loc(x, y, z)); - if (bel == BelId()) - continue; - Loc loc = ctx->getBelLocation(bel); - dbg(" + %d %s\n", z, ctx->nameOfBel(bel)); - log_assert(x == loc.x); - log_assert(y == loc.y); - log_assert(z == loc.z); - usedz.insert(z); - } - - for (BelId bel : ctx->getBelsByTile(x, y)) { - Loc loc = ctx->getBelLocation(bel); - dbg(" - %d %s\n", loc.z, ctx->nameOfBel(bel)); - log_assert(x == loc.x); - log_assert(y == loc.y); - log_assert(usedz.count(loc.z)); - usedz.erase(loc.z); - } - - log_assert(usedz.empty()); - } - - log_break(); -} - -// Implements a LRU cache for pip to wire via getPipsDownhill/getPipsUphill. -// -// This allows a fast way to check getPipsDownhill/getPipsUphill from getPips, -// without balloning memory usage. -struct LruWireCacheMap -{ - LruWireCacheMap(const Context *ctx, size_t cache_size) : ctx(ctx), cache_size(cache_size) - { - cache_hits = 0; - cache_misses = 0; - cache_evictions = 0; - } - - const Context *ctx; - size_t cache_size; - - // Cache stats for checking on cache behavior. - size_t cache_hits; - size_t cache_misses; - size_t cache_evictions; - - // Most recent accessed wires are added to the back of the list, front of - // list is oldest wire in cache. - std::list last_access_list; - // Quick wire -> list element lookup. - dict::iterator> last_access_map; - - dict pips_downhill; - dict pips_uphill; - - void removeWireFromCache(WireId wire_to_remove) - { - for (PipId pip : ctx->getPipsDownhill(wire_to_remove)) { - log_assert(pips_downhill.erase(pip) == 1); - } - - for (PipId pip : ctx->getPipsUphill(wire_to_remove)) { - log_assert(pips_uphill.erase(pip) == 1); - } - } - - void addWireToCache(WireId wire) - { - for (PipId pip : ctx->getPipsDownhill(wire)) { - auto result = pips_downhill.emplace(pip, wire); - log_assert(result.second); - } - - for (PipId pip : ctx->getPipsUphill(wire)) { - auto result = pips_uphill.emplace(pip, wire); - log_assert(result.second); - } - } - - void populateCache(WireId wire) - { - // Put this wire at the end of last_access_list. - auto iter = last_access_list.emplace(last_access_list.end(), wire); - last_access_map.emplace(wire, iter); - - if (last_access_list.size() > cache_size) { - // Cache is full, remove front of last_access_list. - cache_evictions += 1; - WireId wire_to_remove = last_access_list.front(); - last_access_list.pop_front(); - log_assert(last_access_map.erase(wire_to_remove) == 1); - - removeWireFromCache(wire_to_remove); - } - - addWireToCache(wire); - } - - // Determine if wire is in the cache. If wire is not in the cache, - // adds the wire to the cache, and potentially evicts the oldest wire if - // cache is now full. - void checkCache(WireId wire) - { - auto iter = last_access_map.find(wire); - if (iter == last_access_map.end()) { - cache_misses += 1; - populateCache(wire); - } else { - // Record that this wire has been accessed. - cache_hits += 1; - last_access_list.splice(last_access_list.end(), last_access_list, iter->second); - } - } - - // Returns true if pip is uphill of wire (e.g. pip in getPipsUphill(wire)). - bool isPipUphill(PipId pip, WireId wire) - { - checkCache(wire); - return pips_uphill.at(pip) == wire; - } - - // Returns true if pip is downhill of wire (e.g. pip in getPipsDownhill(wire)). - bool isPipDownhill(PipId pip, WireId wire) - { - checkCache(wire); - return pips_downhill.at(pip) == wire; - } - - void cache_info() const - { - log_info("Cache hits: %zu\n", cache_hits); - log_info("Cache misses: %zu\n", cache_misses); - log_info("Cache evictions: %zu\n", cache_evictions); - } -}; - -void archcheck_conn(const Context *ctx) -{ - log_info("Checking connectivity data.\n"); - - log_info("Checking all wires...\n"); - -#ifndef USING_LRU_CACHE - dict pips_downhill; - dict pips_uphill; -#endif - - for (WireId wire : ctx->getWires()) { - for (BelPin belpin : ctx->getWireBelPins(wire)) { - WireId wire2 = ctx->getBelPinWire(belpin.bel, belpin.pin); - log_assert(wire == wire2); - } - - 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 - } - } - - log_info("Checking all BELs...\n"); - for (BelId bel : ctx->getBels()) { - for (IdString pin : ctx->getBelPins(bel)) { - WireId wire = ctx->getBelPinWire(bel, pin); - - if (wire == WireId()) { - continue; - } - - bool found_belpin = false; - for (BelPin belpin : ctx->getWireBelPins(wire)) { - if (belpin.bel == bel && belpin.pin == pin) { - found_belpin = true; - break; - } - } - - 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. - // - // The overhead of maintaining the cache is small relatively to the memory - // gains by avoiding the full pip -> wire map, and still preserves a fast - // 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 - } - } -} - -void archcheck_buckets(const Context *ctx) -{ - log_info("Checking bucket data.\n"); - - // BEL buckets should be subsets of BELs that form an exact cover. - // In particular that means cell types in a bucket should only be - // placable in that bucket. - for (BelBucketId bucket : ctx->getBelBuckets()) { - - // Find out which cell types are in this bucket. - pool cell_types_in_bucket; - for (IdString cell_type : ctx->getCellTypes()) { - if (ctx->getBelBucketForCellType(cell_type) == bucket) { - cell_types_in_bucket.insert(cell_type); - } - } - - // Make sure that all cell types in this bucket have at least one - // BelId they can be placed at. - pool cell_types_unused; - - pool bels_in_bucket; - for (BelId bel : ctx->getBelsInBucket(bucket)) { - BelBucketId bucket2 = ctx->getBelBucketForBel(bel); - log_assert(bucket == bucket2); - - bels_in_bucket.insert(bel); - - // Check to see if a cell type not in this bucket can be - // placed at a BEL in this bucket. - for (IdString cell_type : ctx->getCellTypes()) { - if (ctx->getBelBucketForCellType(cell_type) == bucket) { - if (ctx->isValidBelForCellType(cell_type, bel)) { - cell_types_unused.erase(cell_type); - } - } else { - log_assert(!ctx->isValidBelForCellType(cell_type, bel)); - } - } - } - - // Verify that any BEL not in this bucket reports a different - // bucket. - for (BelId bel : ctx->getBels()) { - if (ctx->getBelBucketForBel(bel) != bucket) { - log_assert(bels_in_bucket.count(bel) == 0); - } - } - - log_assert(cell_types_unused.empty()); - } -} - -} // namespace - -NEXTPNR_NAMESPACE_BEGIN - -void Context::archcheck() const -{ - log_info("Running architecture database integrity check.\n"); - log_break(); - - archcheck_names(this); - archcheck_locs(this); - archcheck_conn(this); - archcheck_buckets(this); -} - -NEXTPNR_NAMESPACE_END diff --git a/common/base_arch.h b/common/base_arch.h deleted file mode 100644 index 3055619d..00000000 --- a/common/base_arch.h +++ /dev/null @@ -1,486 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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 BASE_ARCH_H -#define BASE_ARCH_H - -#include -#include - -#include "arch_api.h" -#include "base_clusterinfo.h" -#include "idstring.h" -#include "nextpnr_types.h" - -NEXTPNR_NAMESPACE_BEGIN - -namespace { -// For several functions; such as bel/wire/pip attributes; the trivial implementation is to return an empty vector -// But an arch might want to do something fancy with a custom range type that doesn't provide a constructor -// So some cursed C++ is needed to return an empty object if possible; or error out if not; is needed -template typename std::enable_if::value, Tc>::type empty_if_possible() -{ - return Tc(); -} -template typename std::enable_if::value, Tc>::type empty_if_possible() -{ - NPNR_ASSERT_FALSE("attempting to use default implementation of range-returning function with range type lacking " - "default constructor!"); -} - -// Provide a default implementation of bel bucket name if typedef'd to IdString -template -typename std::enable_if::value, IdString>::type bbid_to_name(Tbbid id) -{ - return id; -} -template -typename std::enable_if::value, IdString>::type bbid_to_name(Tbbid id) -{ - NPNR_ASSERT_FALSE("getBelBucketName must be implemented when BelBucketId is a type other than IdString!"); -} -template -typename std::enable_if::value, BelBucketId>::type bbid_from_name(IdString name) -{ - return name; -} -template -typename std::enable_if::value, BelBucketId>::type bbid_from_name(IdString name) -{ - NPNR_ASSERT_FALSE("getBelBucketByName must be implemented when BelBucketId is a type other than IdString!"); -} - -// For the cell type and bel type ranges; we want to return our stored vectors only if the type matches -template -typename std::enable_if::value, Tret>::type return_if_match(Tret r) -{ - return r; -} - -template -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&'"); -} - -// Default implementations of the clustering functions -template -typename std::enable_if::value, CellInfo *>::type get_cluster_root(const BaseCtx *ctx, - Tid cluster) -{ - return ctx->cells.at(cluster).get(); -} - -template -typename std::enable_if::value, CellInfo *>::type get_cluster_root(const BaseCtx *ctx, - Tid cluster) -{ - NPNR_ASSERT_FALSE("default implementation of getClusterRootCell requires ClusterId to be IdString"); -} - -// Executes the lambda with the base cluster data, only if the derivation works -template -typename std::enable_if::value, Tret>::type -if_using_basecluster(const Tcell *cell, Tfunc func) -{ - return func(static_cast(cell)); -} -template -typename std::enable_if::value, Tret>::type -if_using_basecluster(const Tcell *cell, Tfunc func) -{ - NPNR_ASSERT_FALSE( - "default implementation of cluster functions requires ArchCellInfo to derive from BaseClusterInfo"); -} - -} // namespace - -// This contains the relevant range types for the default implementations of Arch functions -struct BaseArchRanges -{ - // Bels - using CellBelPinRangeT = std::array; - // Attributes - using BelAttrsRangeT = std::vector>; - using WireAttrsRangeT = std::vector>; - using PipAttrsRangeT = std::vector>; - // Groups - using AllGroupsRangeT = std::vector; - using GroupBelsRangeT = std::vector; - using GroupWiresRangeT = std::vector; - using GroupPipsRangeT = std::vector; - using GroupGroupsRangeT = std::vector; - // Decals - using DecalGfxRangeT = std::vector; - // Placement validity - using CellTypeRangeT = const std::vector &; - using BelBucketRangeT = const std::vector &; - using BucketBelRangeT = const std::vector &; -}; - -template struct BaseArch : ArchAPI -{ - // -------------------------------------------------------------- - // Default, trivial, implementations of Arch API functions for arches that don't need complex behaviours - - // Basic config - virtual IdString archId() const override { return this->id(NPNR_STRINGIFY(ARCHNAME)); } - virtual IdString archArgsToId(typename R::ArchArgsT args) const override { return IdString(); } - virtual int getTilePipDimZ(int x, int y) const override { return 1; } - virtual char getNameDelimiter() const override { return ' '; } - - // Bel methods - virtual uint32_t getBelChecksum(BelId bel) const override { return bel.hash(); } - virtual void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) override - { - NPNR_ASSERT(bel != BelId()); - auto &entry = base_bel2cell[bel]; - NPNR_ASSERT(entry == nullptr); - cell->bel = bel; - cell->belStrength = strength; - entry = cell; - this->refreshUiBel(bel); - } - virtual void unbindBel(BelId bel) override - { - NPNR_ASSERT(bel != BelId()); - auto &entry = base_bel2cell[bel]; - NPNR_ASSERT(entry != nullptr); - entry->bel = BelId(); - entry->belStrength = STRENGTH_NONE; - entry = nullptr; - this->refreshUiBel(bel); - } - - virtual bool getBelHidden(BelId bel) const override { return false; } - - virtual bool getBelGlobalBuf(BelId bel) const override { return false; } - virtual bool checkBelAvail(BelId bel) const override { return getBoundBelCell(bel) == nullptr; }; - virtual CellInfo *getBoundBelCell(BelId bel) const override - { - auto fnd = base_bel2cell.find(bel); - return fnd == base_bel2cell.end() ? nullptr : fnd->second; - } - virtual CellInfo *getConflictingBelCell(BelId bel) const override { return getBoundBelCell(bel); } - virtual typename R::BelAttrsRangeT getBelAttrs(BelId bel) const override - { - return empty_if_possible(); - } - - virtual typename R::CellBelPinRangeT getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const override - { - return return_if_match, typename R::CellBelPinRangeT>({pin}); - } - - // Wire methods - virtual IdString getWireType(WireId wire) const override { return IdString(); } - virtual typename R::WireAttrsRangeT getWireAttrs(WireId) const override - { - return empty_if_possible(); - } - virtual uint32_t getWireChecksum(WireId wire) const override { return wire.hash(); } - - virtual void bindWire(WireId wire, NetInfo *net, PlaceStrength strength) override - { - NPNR_ASSERT(wire != WireId()); - auto &w2n_entry = base_wire2net[wire]; - NPNR_ASSERT(w2n_entry == nullptr); - net->wires[wire].pip = PipId(); - net->wires[wire].strength = strength; - w2n_entry = net; - this->refreshUiWire(wire); - } - virtual void unbindWire(WireId wire) override - { - NPNR_ASSERT(wire != WireId()); - auto &w2n_entry = base_wire2net[wire]; - NPNR_ASSERT(w2n_entry != nullptr); - - auto &net_wires = w2n_entry->wires; - auto it = net_wires.find(wire); - NPNR_ASSERT(it != net_wires.end()); - - auto pip = it->second.pip; - if (pip != PipId()) { - base_pip2net[pip] = nullptr; - } - - net_wires.erase(it); - base_wire2net[wire] = nullptr; - - w2n_entry = nullptr; - this->refreshUiWire(wire); - } - virtual bool checkWireAvail(WireId wire) const override { return getBoundWireNet(wire) == nullptr; } - virtual NetInfo *getBoundWireNet(WireId wire) const override - { - auto fnd = base_wire2net.find(wire); - return fnd == base_wire2net.end() ? nullptr : fnd->second; - } - virtual WireId getConflictingWireWire(WireId wire) const override { return wire; }; - virtual NetInfo *getConflictingWireNet(WireId wire) const override { return getBoundWireNet(wire); } - - // Pip methods - virtual IdString getPipType(PipId pip) const override { return IdString(); } - virtual typename R::PipAttrsRangeT getPipAttrs(PipId) const override - { - return empty_if_possible(); - } - virtual uint32_t getPipChecksum(PipId pip) const override { return pip.hash(); } - virtual void bindPip(PipId pip, NetInfo *net, PlaceStrength strength) override - { - NPNR_ASSERT(pip != PipId()); - auto &p2n_entry = base_pip2net[pip]; - NPNR_ASSERT(p2n_entry == nullptr); - p2n_entry = net; - - WireId dst = this->getPipDstWire(pip); - auto &w2n_entry = base_wire2net[dst]; - NPNR_ASSERT(w2n_entry == nullptr); - w2n_entry = net; - net->wires[dst].pip = pip; - net->wires[dst].strength = strength; - } - virtual void unbindPip(PipId pip) override - { - NPNR_ASSERT(pip != PipId()); - auto &p2n_entry = base_pip2net[pip]; - NPNR_ASSERT(p2n_entry != nullptr); - WireId dst = this->getPipDstWire(pip); - - auto &w2n_entry = base_wire2net[dst]; - NPNR_ASSERT(w2n_entry != nullptr); - w2n_entry = nullptr; - - p2n_entry->wires.erase(dst); - p2n_entry = nullptr; - } - virtual bool checkPipAvail(PipId pip) const override { return getBoundPipNet(pip) == nullptr; } - virtual bool checkPipAvailForNet(PipId pip, NetInfo *net) const override - { - NetInfo *bound_net = getBoundPipNet(pip); - return bound_net == nullptr || bound_net == net; - } - virtual NetInfo *getBoundPipNet(PipId pip) const override - { - auto fnd = base_pip2net.find(pip); - return fnd == base_pip2net.end() ? nullptr : fnd->second; - } - virtual WireId getConflictingPipWire(PipId pip) const override { return WireId(); } - virtual NetInfo *getConflictingPipNet(PipId pip) const override { return getBoundPipNet(pip); } - - // Group methods - virtual GroupId getGroupByName(IdStringList name) const override { return GroupId(); }; - virtual IdStringList getGroupName(GroupId group) const override { return IdStringList(); }; - virtual typename R::AllGroupsRangeT getGroups() const override - { - return empty_if_possible(); - } - // Default implementation of these assumes no groups so never called - virtual typename R::GroupBelsRangeT getGroupBels(GroupId group) const override - { - NPNR_ASSERT_FALSE("unreachable"); - }; - virtual typename R::GroupWiresRangeT getGroupWires(GroupId group) const override - { - NPNR_ASSERT_FALSE("unreachable"); - }; - virtual typename R::GroupPipsRangeT getGroupPips(GroupId group) const override - { - NPNR_ASSERT_FALSE("unreachable"); - }; - virtual typename R::GroupGroupsRangeT getGroupGroups(GroupId group) const override - { - NPNR_ASSERT_FALSE("unreachable"); - }; - - // Delay methods - virtual bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const override - { - return false; - } - - // Decal methods - virtual typename R::DecalGfxRangeT getDecalGraphics(DecalId decal) const override - { - return empty_if_possible(); - }; - virtual DecalXY getBelDecal(BelId bel) const override { return DecalXY(); } - virtual DecalXY getWireDecal(WireId wire) const override { return DecalXY(); } - virtual DecalXY getPipDecal(PipId pip) const override { return DecalXY(); } - virtual DecalXY getGroupDecal(GroupId group) const override { return DecalXY(); } - - // Cell timing methods - virtual bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const override - { - return false; - } - virtual TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const override - { - return TMG_IGNORE; - } - virtual TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const override - { - NPNR_ASSERT_FALSE("unreachable"); - } - - // Placement validity checks - virtual bool isValidBelForCellType(IdString cell_type, BelId bel) const override - { - return cell_type == this->getBelType(bel); - } - virtual IdString getBelBucketName(BelBucketId bucket) const override { return bbid_to_name(bucket); } - virtual BelBucketId getBelBucketByName(IdString name) const override { return bbid_from_name(name); } - virtual BelBucketId getBelBucketForBel(BelId bel) const override - { - return getBelBucketForCellType(this->getBelType(bel)); - }; - virtual BelBucketId getBelBucketForCellType(IdString cell_type) const override - { - return getBelBucketByName(cell_type); - }; - virtual bool isBelLocationValid(BelId bel) const override { return true; } - virtual typename R::CellTypeRangeT getCellTypes() const override - { - NPNR_ASSERT(cell_types_initialised); - return return_if_match &, typename R::CellTypeRangeT>(cell_types); - } - virtual typename R::BelBucketRangeT getBelBuckets() const override - { - NPNR_ASSERT(bel_buckets_initialised); - return return_if_match &, typename R::BelBucketRangeT>(bel_buckets); - } - virtual typename R::BucketBelRangeT getBelsInBucket(BelBucketId bucket) const override - { - NPNR_ASSERT(bel_buckets_initialised); - return return_if_match &, typename R::BucketBelRangeT>(bucket_bels.at(bucket)); - } - - // Cluster methods - virtual CellInfo *getClusterRootCell(ClusterId cluster) const override { return get_cluster_root(this, cluster); } - - virtual ArcBounds getClusterBounds(ClusterId cluster) const override - { - return if_using_basecluster(get_cluster_root(this, cluster), [](const BaseClusterInfo *cluster) { - ArcBounds bounds(0, 0, 0, 0); - for (auto child : cluster->constr_children) { - if_using_basecluster(child, [&](const BaseClusterInfo *child) { - bounds.x0 = std::min(bounds.x0, child->constr_x); - bounds.y0 = std::min(bounds.y0, child->constr_y); - bounds.x1 = std::max(bounds.x1, child->constr_x); - bounds.y1 = std::max(bounds.y1, child->constr_y); - }); - } - return bounds; - }); - } - - virtual Loc getClusterOffset(const CellInfo *cell) const override - { - return if_using_basecluster(cell, - [](const BaseClusterInfo *c) { return Loc(c->constr_x, c->constr_y, 0); }); - } - - virtual bool isClusterStrict(const CellInfo *cell) const override { return true; } - - virtual bool getClusterPlacement(ClusterId cluster, BelId root_bel, - std::vector> &placement) const override - { - CellInfo *root_cell = get_cluster_root(this, cluster); - return if_using_basecluster(root_cell, [&](const BaseClusterInfo *cluster) -> bool { - placement.clear(); - NPNR_ASSERT(root_bel != BelId()); - Loc root_loc = this->getBelLocation(root_bel); - - if (cluster->constr_abs_z) { - // Coerce root to absolute z constraint - root_loc.z = cluster->constr_z; - root_bel = this->getBelByLocation(root_loc); - if (root_bel == BelId() || !this->isValidBelForCellType(root_cell->type, root_bel)) - return false; - } - placement.emplace_back(root_cell, root_bel); - - for (auto child : cluster->constr_children) { - Loc child_loc = if_using_basecluster(child, [&](const BaseClusterInfo *child) { - Loc result; - result.x = root_loc.x + child->constr_x; - result.y = root_loc.y + child->constr_y; - result.z = child->constr_abs_z ? child->constr_z : (root_loc.z + child->constr_z); - return result; - }); - BelId child_bel = this->getBelByLocation(child_loc); - if (child_bel == BelId() || !this->isValidBelForCellType(child->type, child_bel)) - return false; - placement.emplace_back(child, child_bel); - } - return true; - }); - } - - // Flow methods - virtual void assignArchInfo() override{}; - - // -------------------------------------------------------------- - // These structures are used to provide default implementations of bel/wire/pip binding. Arches might want to - // replace them with their own, for example to use faster access structures than dict. Arches might also - // want to add extra checks around these functions - dict base_bel2cell; - dict base_wire2net; - dict base_pip2net; - - // For the default cell/bel bucket implementations - std::vector cell_types; - std::vector bel_buckets; - dict> bucket_bels; - - // Arches that want to use the default cell types and bel buckets *must* call these functions in their constructor - bool cell_types_initialised = false; - bool bel_buckets_initialised = false; - void init_cell_types() - { - pool bel_types; - for (auto bel : this->getBels()) - bel_types.insert(this->getBelType(bel)); - std::copy(bel_types.begin(), bel_types.end(), std::back_inserter(cell_types)); - std::sort(cell_types.begin(), cell_types.end()); - cell_types_initialised = true; - } - void init_bel_buckets() - { - for (auto cell_type : this->getCellTypes()) { - auto bucket = this->getBelBucketForCellType(cell_type); - bucket_bels[bucket]; // create empty bucket - } - for (auto bel : this->getBels()) { - auto bucket = this->getBelBucketForBel(bel); - bucket_bels[bucket].push_back(bel); - } - for (auto &b : bucket_bels) - bel_buckets.push_back(b.first); - std::sort(bel_buckets.begin(), bel_buckets.end()); - bel_buckets_initialised = true; - } -}; - -NEXTPNR_NAMESPACE_END - -#endif /* BASE_ARCH_H */ diff --git a/common/base_clusterinfo.h b/common/base_clusterinfo.h deleted file mode 100644 index 65e8e6d4..00000000 --- a/common/base_clusterinfo.h +++ /dev/null @@ -1,45 +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. - * - */ - -#ifndef BASE_CLUSTERINFO_H -#define BASE_CLUSTERINFO_H - -#include "idstring.h" -#include "nextpnr_namespaces.h" - -#include - -NEXTPNR_NAMESPACE_BEGIN - -struct CellInfo; - -// The 'legacy' cluster data, used for existing arches and to provide a basic implementation for arches without complex -// clustering requirements -struct BaseClusterInfo -{ - std::vector constr_children; - int constr_x = 0; // this.x - parent.x - int constr_y = 0; // this.y - parent.y - int constr_z = 0; // this.z - parent.z - bool constr_abs_z = false; // parent.z := 0 -}; - -NEXTPNR_NAMESPACE_END - -#endif /* BASE_ARCH_H */ diff --git a/common/basectx.cc b/common/basectx.cc deleted file mode 100644 index 83a2deea..00000000 --- a/common/basectx.cc +++ /dev/null @@ -1,279 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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 "basectx.h" - -#include - -#include "context.h" -#include "log.h" - -NEXTPNR_NAMESPACE_BEGIN - -const char *BaseCtx::nameOfBel(BelId bel) const -{ - const Context *ctx = getCtx(); - std::string &s = ctx->log_strs.next(); - ctx->getBelName(bel).build_str(ctx, s); - return s.c_str(); -} - -const char *BaseCtx::nameOfWire(WireId wire) const -{ - const Context *ctx = getCtx(); - std::string &s = ctx->log_strs.next(); - ctx->getWireName(wire).build_str(ctx, s); - return s.c_str(); -} - -const char *BaseCtx::nameOfPip(PipId pip) const -{ - const Context *ctx = getCtx(); - std::string &s = ctx->log_strs.next(); - ctx->getPipName(pip).build_str(ctx, s); - return s.c_str(); -} - -const char *BaseCtx::nameOfGroup(GroupId group) const -{ - const Context *ctx = getCtx(); - std::string &s = ctx->log_strs.next(); - ctx->getGroupName(group).build_str(ctx, s); - return s.c_str(); -} - -BelId BaseCtx::getBelByNameStr(const std::string &str) -{ - Context *ctx = getCtx(); - return ctx->getBelByName(IdStringList::parse(ctx, str)); -} - -WireId BaseCtx::getWireByNameStr(const std::string &str) -{ - Context *ctx = getCtx(); - return ctx->getWireByName(IdStringList::parse(ctx, str)); -} - -PipId BaseCtx::getPipByNameStr(const std::string &str) -{ - Context *ctx = getCtx(); - return ctx->getPipByName(IdStringList::parse(ctx, str)); -} - -GroupId BaseCtx::getGroupByNameStr(const std::string &str) -{ - Context *ctx = getCtx(); - return ctx->getGroupByName(IdStringList::parse(ctx, str)); -} - -void BaseCtx::addClock(IdString net, float freq) -{ - std::unique_ptr cc(new ClockConstraint()); - cc->period = DelayPair(getCtx()->getDelayFromNS(1000 / freq)); - cc->high = DelayPair(getCtx()->getDelayFromNS(500 / freq)); - cc->low = DelayPair(getCtx()->getDelayFromNS(500 / freq)); - if (!net_aliases.count(net)) { - log_warning("net '%s' does not exist in design, ignoring clock constraint\n", net.c_str(this)); - } else { - getNetByAlias(net)->clkconstr = std::move(cc); - log_info("constraining clock net '%s' to %.02f MHz\n", net.c_str(this), freq); - } -} - -void BaseCtx::createRectangularRegion(IdString name, int x0, int y0, int x1, int y1) -{ - std::unique_ptr new_region(new Region()); - new_region->name = name; - new_region->constr_bels = true; - new_region->constr_pips = false; - new_region->constr_wires = false; - for (int x = x0; x <= x1; x++) { - for (int y = y0; y <= y1; y++) { - for (auto bel : getCtx()->getBelsByTile(x, y)) - new_region->bels.insert(bel); - } - } - region[name] = std::move(new_region); -} -void BaseCtx::addBelToRegion(IdString name, BelId bel) { region[name]->bels.insert(bel); } -void BaseCtx::constrainCellToRegion(IdString cell, IdString region_name) -{ - // Support hierarchical cells as well as leaf ones - bool matched = false; - if (hierarchy.count(cell)) { - auto &hc = hierarchy.at(cell); - for (auto &lc : hc.leaf_cells) - constrainCellToRegion(lc.second, region_name); - for (auto &hsc : hc.hier_cells) - constrainCellToRegion(hsc.second, region_name); - matched = true; - } - if (cells.count(cell)) { - cells.at(cell)->region = region[region_name].get(); - matched = true; - } - if (!matched) - log_warning("No cell matched '%s' when constraining to region '%s'\n", nameOf(cell), nameOf(region_name)); -} -DecalXY BaseCtx::constructDecalXY(DecalId decal, float x, float y) -{ - DecalXY dxy; - dxy.decal = decal; - dxy.x = x; - dxy.y = y; - return dxy; -} - -void BaseCtx::archInfoToAttributes() -{ - for (auto &cell : cells) { - auto ci = cell.second.get(); - if (ci->bel != BelId()) { - if (ci->attrs.find(id("BEL")) != ci->attrs.end()) { - ci->attrs.erase(ci->attrs.find(id("BEL"))); - } - ci->attrs[id("NEXTPNR_BEL")] = getCtx()->getBelName(ci->bel).str(getCtx()); - ci->attrs[id("BEL_STRENGTH")] = (int)ci->belStrength; - } - } - for (auto &net : getCtx()->nets) { - auto ni = net.second.get(); - std::string routing; - bool first = true; - for (auto &item : ni->wires) { - if (!first) - routing += ";"; - routing += getCtx()->getWireName(item.first).str(getCtx()); - routing += ";"; - if (item.second.pip != PipId()) - routing += getCtx()->getPipName(item.second.pip).str(getCtx()); - routing += ";" + std::to_string(item.second.strength); - first = false; - } - ni->attrs[id("ROUTING")] = routing; - } -} - -void BaseCtx::attributesToArchInfo() -{ - for (auto &cell : cells) { - auto ci = cell.second.get(); - auto val = ci->attrs.find(id("NEXTPNR_BEL")); - if (val != ci->attrs.end()) { - auto str = ci->attrs.find(id("BEL_STRENGTH")); - PlaceStrength strength = PlaceStrength::STRENGTH_USER; - if (str != ci->attrs.end()) - strength = (PlaceStrength)str->second.as_int64(); - - BelId b = getCtx()->getBelByNameStr(val->second.as_string()); - getCtx()->bindBel(b, ci, strength); - } - } - for (auto &net : getCtx()->nets) { - auto ni = net.second.get(); - auto val = ni->attrs.find(id("ROUTING")); - if (val != ni->attrs.end()) { - std::vector strs; - auto routing = val->second.as_string(); - boost::split(strs, routing, boost::is_any_of(";")); - for (size_t i = 0; i < strs.size() / 3; i++) { - std::string wire = strs[i * 3]; - std::string pip = strs[i * 3 + 1]; - PlaceStrength strength = (PlaceStrength)std::stoi(strs[i * 3 + 2]); - if (pip.empty()) - getCtx()->bindWire(getCtx()->getWireByName(IdStringList::parse(getCtx(), wire)), ni, strength); - else - getCtx()->bindPip(getCtx()->getPipByName(IdStringList::parse(getCtx(), pip)), ni, strength); - } - } - } - getCtx()->assignArchInfo(); -} - -NetInfo *BaseCtx::createNet(IdString name) -{ - NPNR_ASSERT(!nets.count(name)); - NPNR_ASSERT(!net_aliases.count(name)); - auto net = std::make_unique(name); - net_aliases[name] = name; - NetInfo *ptr = net.get(); - nets[name] = std::move(net); - refreshUi(); - return ptr; -} - -void BaseCtx::connectPort(IdString net, IdString cell, IdString port) -{ - NetInfo *net_info = getNetByAlias(net); - CellInfo *cell_info = cells.at(cell).get(); - cell_info->connectPort(port, net_info); -} - -void BaseCtx::disconnectPort(IdString cell, IdString port) -{ - CellInfo *cell_info = cells.at(cell).get(); - cell_info->disconnectPort(port); -} - -void BaseCtx::renameNet(IdString old_name, IdString new_name) -{ - NetInfo *net = nets.at(old_name).get(); - NPNR_ASSERT(!nets.count(new_name)); - nets[new_name]; - std::swap(nets.at(net->name), nets.at(new_name)); - nets.erase(net->name); - net->name = new_name; -} - -void BaseCtx::ripupNet(IdString name) -{ - NetInfo *net_info = getNetByAlias(name); - std::vector to_unbind; - for (auto &wire : net_info->wires) - to_unbind.push_back(wire.first); - for (auto &unbind : to_unbind) - getCtx()->unbindWire(unbind); -} -void BaseCtx::lockNetRouting(IdString name) -{ - NetInfo *net_info = getNetByAlias(name); - for (auto &wire : net_info->wires) - wire.second.strength = STRENGTH_USER; -} - -CellInfo *BaseCtx::createCell(IdString name, IdString type) -{ - NPNR_ASSERT(!cells.count(name)); - auto cell = std::make_unique(getCtx(), name, type); - CellInfo *ptr = cell.get(); - cells[name] = std::move(cell); - refreshUi(); - return ptr; -} - -void BaseCtx::copyBelPorts(IdString cell, BelId bel) -{ - CellInfo *cell_info = cells.at(cell).get(); - for (auto pin : getCtx()->getBelPins(bel)) { - cell_info->ports[pin].name = pin; - cell_info->ports[pin].type = getCtx()->getBelPinType(bel, pin); - } -} - -NEXTPNR_NAMESPACE_END diff --git a/common/basectx.h b/common/basectx.h deleted file mode 100644 index 21d6d63a..00000000 --- a/common/basectx.h +++ /dev/null @@ -1,243 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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 BASECTX_H -#define BASECTX_H - -#include -#include -#include -#ifndef NPNR_DISABLE_THREADS -#include -#endif - -#include "hashlib.h" -#include "idstring.h" -#include "nextpnr_namespaces.h" -#include "nextpnr_types.h" -#include "property.h" -#include "str_ring_buffer.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct Context; - -struct BaseCtx -{ -#ifndef NPNR_DISABLE_THREADS - // Lock to perform mutating actions on the Context. - std::mutex mutex; - boost::thread::id mutex_owner; - - // Lock to be taken by UI when wanting to access context - the yield() - // method will lock/unlock it when its' released the main mutex to make - // sure the UI is not starved. - std::mutex ui_mutex; -#endif - - // ID String database. - mutable std::unordered_map *idstring_str_to_idx; - mutable std::vector *idstring_idx_to_str; - - // Temporary string backing store for logging - mutable StrRingBuffer log_strs; - - // Project settings and config switches - dict settings; - - // Placed nets and cells. - dict> nets; - dict> cells; - - // Hierarchical (non-leaf) cells by full path - dict hierarchy; - // This is the root of the above structure - IdString top_module; - - // Aliases for nets, which may have more than one name due to assignments and hierarchy - dict net_aliases; - - // Top-level ports - dict ports; - dict port_cells; - - // Floorplanning regions - dict> region; - - // Context meta data - dict attrs; - - // Fmax data post timing analysis - TimingResult timing_result; - - Context *as_ctx = nullptr; - - // Has the frontend loaded a design? - bool design_loaded; - - BaseCtx() - { - idstring_str_to_idx = new std::unordered_map; - idstring_idx_to_str = new std::vector; - IdString::initialize_add(this, "", 0); - IdString::initialize_arch(this); - - design_loaded = false; - } - - virtual ~BaseCtx() - { - delete idstring_str_to_idx; - delete idstring_idx_to_str; - } - - // Must be called before performing any mutating changes on the Ctx/Arch. - void lock(void) - { -#ifndef NPNR_DISABLE_THREADS - mutex.lock(); - mutex_owner = boost::this_thread::get_id(); -#endif - } - - void unlock(void) - { -#ifndef NPNR_DISABLE_THREADS - NPNR_ASSERT(boost::this_thread::get_id() == mutex_owner); - mutex.unlock(); -#endif - } - - // Must be called by the UI before rendering data. This lock will be - // prioritized when processing code calls yield(). - void lock_ui(void) - { -#ifndef NPNR_DISABLE_THREADS - ui_mutex.lock(); - mutex.lock(); -#endif - } - - void unlock_ui(void) - { -#ifndef NPNR_DISABLE_THREADS - mutex.unlock(); - ui_mutex.unlock(); -#endif - } - - // Yield to UI by unlocking the main mutex, flashing the UI mutex and - // relocking the main mutex. Call this when you're performing a - // long-standing action while holding a lock to let the UI show - // visualization updates. - // Must be called with the main lock taken. - void yield(void) - { -#ifndef NPNR_DISABLE_THREADS - unlock(); - ui_mutex.lock(); - ui_mutex.unlock(); - lock(); -#endif - } - - IdString id(const std::string &s) const { return IdString(this, s); } - - IdString id(const char *s) const { return IdString(this, s); } - - Context *getCtx() { return as_ctx; } - - const Context *getCtx() const { return as_ctx; } - - const char *nameOf(IdString name) const { return name.c_str(this); } - - template const char *nameOf(const T *obj) const - { - if (obj == nullptr) - return ""; - return obj->name.c_str(this); - } - - const char *nameOfBel(BelId bel) const; - const char *nameOfWire(WireId wire) const; - const char *nameOfPip(PipId pip) const; - const char *nameOfGroup(GroupId group) const; - - // Wrappers of arch functions that take a string and handle IdStringList parsing - BelId getBelByNameStr(const std::string &str); - WireId getWireByNameStr(const std::string &str); - PipId getPipByNameStr(const std::string &str); - GroupId getGroupByNameStr(const std::string &str); - - // -------------------------------------------------------------- - - bool allUiReload = true; - bool frameUiReload = false; - pool belUiReload; - pool wireUiReload; - pool pipUiReload; - pool groupUiReload; - - void refreshUi() { allUiReload = true; } - - void refreshUiFrame() { frameUiReload = true; } - - void refreshUiBel(BelId bel) { belUiReload.insert(bel); } - - void refreshUiWire(WireId wire) { wireUiReload.insert(wire); } - - void refreshUiPip(PipId pip) { pipUiReload.insert(pip); } - - void refreshUiGroup(GroupId group) { groupUiReload.insert(group); } - - // -------------------------------------------------------------- - - NetInfo *getNetByAlias(IdString alias) const - { - return nets.count(alias) ? nets.at(alias).get() : nets.at(net_aliases.at(alias)).get(); - } - - // Intended to simplify Python API - void addClock(IdString net, float freq); - void createRectangularRegion(IdString name, int x0, int y0, int x1, int y1); - void addBelToRegion(IdString name, BelId bel); - void constrainCellToRegion(IdString cell, IdString region_name); - - // Helper functions for Python bindings - NetInfo *createNet(IdString name); - void connectPort(IdString net, IdString cell, IdString port); - void disconnectPort(IdString cell, IdString port); - void ripupNet(IdString name); - void lockNetRouting(IdString name); - void renameNet(IdString old_name, IdString new_name); - - CellInfo *createCell(IdString name, IdString type); - void copyBelPorts(IdString cell, BelId bel); - - // Workaround for lack of wrappable constructors - DecalXY constructDecalXY(DecalId decal, float x, float y); - - void archInfoToAttributes(); - void attributesToArchInfo(); -}; - -NEXTPNR_NAMESPACE_END - -#endif /* BASECTX_H */ diff --git a/common/bits.cc b/common/bits.cc deleted file mode 100644 index b20c2e86..00000000 --- a/common/bits.cc +++ /dev/null @@ -1,56 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (c) 2013 Mike Pedersen - * Copyright (C) 2021 Symbiflow Authors - * - * 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 "bits.h" - -#include -#include - -#include "log.h" - -NEXTPNR_NAMESPACE_BEGIN - -int Bits::generic_popcount(unsigned int v) -{ - unsigned int c; // c accumulates the total bits set in v - for (c = 0; v; c++) { - v &= v - 1; // clear the least significant bit set - } - - return c; -} - -int Bits::generic_ctz(unsigned int x) -{ - if (x == 0) { - log_error("Cannot call ctz with arg = 0"); - } - - for (size_t i = 0; i < std::numeric_limits::digits; ++i) { - if ((x & (1 << i)) != 0) { - return i; - } - } - - // Unreachable! - log_error("Unreachable!"); -} - -NEXTPNR_NAMESPACE_END diff --git a/common/bits.h b/common/bits.h deleted file mode 100644 index 04b25b74..00000000 --- a/common/bits.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (c) 2013 Mike Pedersen - * Copyright (C) 2021 Symbiflow Authors - * - * 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. - * - */ - -// This is a small library for implementing common bit vector utilities, -// namely: -// -// - popcount : The number of bits set in an unsigned int -// - ctz : The number of trailing zero bits in an unsigned int. -// Must be called with a value that has at least 1 bit set. -// -// These methods will typically use instrinics when available, and have a -// generic fallback in the event that the instrinic is not available. -// -// If clz (count leading zeros) is needed, it can be added when needed. -#ifndef BITS_H -#define BITS_H - -#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) -#include -#pragma intrinsic(_BitScanForward, _BitScanReverse, __popcnt) -#endif - -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct Bits -{ - static int generic_popcount(unsigned int x); - static int generic_ctz(unsigned int x); - - static int popcount(unsigned int x) - { -#if defined(__GNUC__) || defined(__clang__) - return __builtin_popcount(x); -#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) - return __popcnt(x); -#else - return generic_popcount(x); -#endif - } - - static int ctz(unsigned int x) - { -#if defined(__GNUC__) || defined(__clang__) - return __builtin_ctz(x); -#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) - unsigned long result; - _BitScanForward(&result, x); - return result; -#else - return generic_ctz(x); -#endif - } -}; - -NEXTPNR_NAMESPACE_END - -#endif /* BITS_H */ diff --git a/common/chain_utils.h b/common/chain_utils.h deleted file mode 100644 index ca8a1be3..00000000 --- a/common/chain_utils.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * 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 CHAIN_UTILS_H -#define CHAIN_UTILS_H - -#include "nextpnr.h" -#include "util.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct CellChain -{ - std::vector cells; -}; - -// Generic chain finder -template -std::vector find_chains(const Context *ctx, F1 cell_type_predicate, F2 get_previous, F3 get_next, - size_t min_length = 2) -{ - std::set chained; - std::vector chains; - for (auto &cell : ctx->cells) { - if (chained.find(cell.first) != chained.end()) - continue; - CellInfo *ci = cell.second.get(); - if (cell_type_predicate(ctx, ci)) { - CellInfo *start = ci; - CellInfo *prev_start = ci; - while (prev_start != nullptr) { - start = prev_start; - prev_start = get_previous(ctx, start); - } - CellChain chain; - CellInfo *end = start; - while (end != nullptr) { - if (chained.insert(end->name).second) - chain.cells.push_back(end); - end = get_next(ctx, end); - } - if (chain.cells.size() >= min_length) { - chains.push_back(chain); - for (auto c : chain.cells) - chained.insert(c->name); - } - } - } - return chains; -} - -NEXTPNR_NAMESPACE_END -#endif diff --git a/common/command.cc b/common/command.cc deleted file mode 100644 index 00f900b3..00000000 --- a/common/command.cc +++ /dev/null @@ -1,563 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * 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 NO_GUI -#include -#include "application.h" -#include "mainwindow.h" -#endif -#ifndef NO_PYTHON -#include "pybindings.h" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include "command.h" -#include "design_utils.h" -#include "json_frontend.h" -#include "jsonwrite.h" -#include "log.h" -#include "timing.h" -#include "util.h" -#include "version.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct no_separator : std::numpunct -{ - protected: - virtual string_type do_grouping() const { return "\000"; } // groups of 0 (disable) -}; - -CommandHandler::CommandHandler(int argc, char **argv) : argc(argc), argv(argv) -{ - try { - std::locale loc(""); - std::locale::global(std::locale(loc, new no_separator())); - } catch (const std::runtime_error &e) { - // the locale is broken in this system, so leave it as it is - } - log_streams.clear(); -} - -bool CommandHandler::parseOptions() -{ - options.add(getGeneralOptions()).add(getArchOptions()); - try { - po::parsed_options parsed = - po::command_line_parser(argc, argv) - .style(po::command_line_style::default_style ^ po::command_line_style::allow_guessing) - .options(options) - .positional(pos) - .run(); - po::store(parsed, vm); - po::notify(vm); - return true; - } catch (std::exception &e) { - std::cout << e.what() << "\n"; - return false; - } -} - -bool CommandHandler::executeBeforeContext() -{ - if (vm.count("help") || argc == 1) { - std::cerr << boost::filesystem::basename(argv[0]) - << " -- Next Generation Place and Route (Version " GIT_DESCRIBE_STR ")\n"; - std::cerr << options << "\n"; - return argc != 1; - } - - if (vm.count("version")) { - std::cerr << boost::filesystem::basename(argv[0]) - << " -- Next Generation Place and Route (Version " GIT_DESCRIBE_STR ")\n"; - return true; - } - validate(); - - if (vm.count("quiet")) { - log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING_MSG)); - } else { - log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG_MSG)); - } - - if (vm.count("log")) { - std::string logfilename = vm["log"].as(); - logfile.open(logfilename); - if (!logfile.is_open()) - log_error("Failed to open log file '%s' for writing.\n", logfilename.c_str()); - log_streams.push_back(std::make_pair(&logfile, LogLevel::LOG_MSG)); - } - return false; -} - -po::options_description CommandHandler::getGeneralOptions() -{ - po::options_description general("General options"); - general.add_options()("help,h", "show help"); - general.add_options()("verbose,v", "verbose output"); - general.add_options()("quiet,q", "quiet mode, only errors and warnings displayed"); - general.add_options()("log,l", po::value(), - "log file, all log messages are written to this file regardless of -q"); - general.add_options()("debug", "debug output"); - general.add_options()("debug-placer", "debug output from placer only"); - general.add_options()("debug-router", "debug output from router only"); - general.add_options()("threads", po::value(), "number of threads for passes where this is configurable"); - - general.add_options()("force,f", "keep running after errors"); -#ifndef NO_GUI - general.add_options()("gui", "start gui"); - general.add_options()("gui-no-aa", "disable anti aliasing (use together with --gui option)"); -#endif -#ifndef NO_PYTHON - general.add_options()("run", po::value>(), - "python file to execute instead of default flow"); - pos.add("run", -1); - general.add_options()("pre-pack", po::value>(), "python file to run before packing"); - general.add_options()("pre-place", po::value>(), "python file to run before placement"); - general.add_options()("pre-route", po::value>(), "python file to run before routing"); - general.add_options()("post-route", po::value>(), "python file to run after routing"); - general.add_options()("on-failure", po::value>(), - "python file to run in event of crash for design introspection"); - -#endif - general.add_options()("json", po::value(), "JSON design file to ingest"); - general.add_options()("write", po::value(), "JSON design file to write"); - general.add_options()("top", po::value(), "name of top module"); - general.add_options()("seed", po::value(), "seed value for random number generator"); - general.add_options()("randomize-seed,r", "randomize seed value for random number generator"); - - general.add_options()( - "placer", po::value(), - std::string("placer algorithm to use; available: " + boost::algorithm::join(Arch::availablePlacers, ", ") + - "; default: " + Arch::defaultPlacer) - .c_str()); - - general.add_options()( - "router", po::value(), - std::string("router algorithm to use; available: " + boost::algorithm::join(Arch::availableRouters, ", ") + - "; default: " + Arch::defaultRouter) - .c_str()); - - general.add_options()("slack_redist_iter", po::value(), "number of iterations between slack redistribution"); - general.add_options()("cstrweight", po::value(), "placer weighting for relative constraint satisfaction"); - general.add_options()("starttemp", po::value(), "placer SA start temperature"); - general.add_options()("placer-budgets", "use budget rather than criticality in placer timing weights"); - - general.add_options()("pack-only", "pack design only without placement or routing"); - general.add_options()("no-route", "process design without routing"); - general.add_options()("no-place", "process design without placement"); - general.add_options()("no-pack", "process design without packing"); - - general.add_options()("ignore-loops", "ignore combinational loops in timing analysis"); - - general.add_options()("version,V", "show version"); - general.add_options()("test", "check architecture database integrity"); - general.add_options()("freq", po::value(), "set target frequency for design in MHz"); - general.add_options()("timing-allow-fail", "allow timing to fail in design"); - general.add_options()("no-tmdriv", "disable timing-driven placement"); - general.add_options()("sdf", po::value(), "SDF delay back-annotation file to write"); - general.add_options()("sdf-cvc", "enable tweaks for SDF file compatibility with the CVC simulator"); - general.add_options()("no-print-critical-path-source", - "disable printing of the line numbers associated with each net in the critical path"); - - general.add_options()("placer-heap-alpha", po::value(), "placer heap alpha value (float, default: 0.1)"); - general.add_options()("placer-heap-beta", po::value(), "placer heap beta value (float, default: 0.9)"); - general.add_options()("placer-heap-critexp", po::value(), - "placer heap criticality exponent (int, default: 2)"); - general.add_options()("placer-heap-timingweight", po::value(), "placer heap timing weight (int, default: 10)"); - -#if !defined(__wasm) - general.add_options()("parallel-refine", "use new experimental parallelised engine for placement refinement"); -#endif - - general.add_options()("router2-heatmap", po::value(), - "prefix for router2 resource congestion heatmaps"); - - general.add_options()("tmg-ripup", "enable experimental timing-driven ripup in router"); - general.add_options()("router2-tmg-ripup", - "enable experimental timing-driven ripup in router (deprecated; use --tmg-ripup instead)"); - - general.add_options()("report", po::value(), - "write timing and utilization report in JSON format to file"); - general.add_options()("detailed-timing-report", "Append detailed net timing data to the JSON report"); - - general.add_options()("placed-svg", po::value(), "write render of placement to SVG file"); - general.add_options()("routed-svg", po::value(), "write render of routing to SVG file"); - - return general; -} - -namespace { -static CommandHandler *global_command_handler = nullptr; -void script_terminate_handler() -{ - if (global_command_handler != nullptr) - global_command_handler->run_script_hook("on-failure"); -} -}; // namespace - -void CommandHandler::setupContext(Context *ctx) -{ - if (ctx->settings.find(ctx->id("seed")) != ctx->settings.end()) - ctx->rngstate = ctx->setting("seed"); - - if (vm.count("verbose")) { - ctx->verbose = true; - } - - if (vm.count("debug")) { - ctx->verbose = true; - ctx->debug = true; - } - - if (vm.count("no-print-critical-path-source")) { - ctx->disable_critical_path_source_print = true; - } - - if (vm.count("force")) { - ctx->force = true; - } - - if (vm.count("seed")) { - ctx->rngseed(vm["seed"].as()); - } - - if (vm.count("threads")) { - ctx->settings[ctx->id("threads")] = vm["threads"].as(); - } - - if (vm.count("randomize-seed")) { - std::random_device randDev{}; - std::uniform_int_distribution distrib{1}; - ctx->rngseed(distrib(randDev)); - } - - if (vm.count("slack_redist_iter")) { - ctx->settings[ctx->id("slack_redist_iter")] = vm["slack_redist_iter"].as(); - if (vm.count("freq") && vm["freq"].as() == 0) { - ctx->settings[ctx->id("auto_freq")] = true; -#ifndef NO_GUI - if (!vm.count("gui")) -#endif - log_warning("Target frequency not specified. Will optimise for max frequency.\n"); - } - } - - if (vm.count("ignore-loops")) { - ctx->settings[ctx->id("timing/ignoreLoops")] = true; - } - - if (vm.count("timing-allow-fail")) { - ctx->settings[ctx->id("timing/allowFail")] = true; - } - - if (vm.count("placer")) { - std::string placer = vm["placer"].as(); - if (std::find(Arch::availablePlacers.begin(), Arch::availablePlacers.end(), placer) == - Arch::availablePlacers.end()) - log_error("Placer algorithm '%s' is not supported (available options: %s)\n", placer.c_str(), - boost::algorithm::join(Arch::availablePlacers, ", ").c_str()); - ctx->settings[ctx->id("placer")] = placer; - } - - if (vm.count("router")) { - std::string router = vm["router"].as(); - if (std::find(Arch::availableRouters.begin(), Arch::availableRouters.end(), router) == - Arch::availableRouters.end()) - log_error("Router algorithm '%s' is not supported (available options: %s)\n", router.c_str(), - boost::algorithm::join(Arch::availableRouters, ", ").c_str()); - ctx->settings[ctx->id("router")] = router; - } - - if (vm.count("cstrweight")) { - ctx->settings[ctx->id("placer1/constraintWeight")] = std::to_string(vm["cstrweight"].as()); - } - if (vm.count("starttemp")) { - ctx->settings[ctx->id("placer1/startTemp")] = std::to_string(vm["starttemp"].as()); - } - - if (vm.count("placer-budgets")) { - ctx->settings[ctx->id("placer1/budgetBased")] = true; - } - if (vm.count("freq")) { - auto freq = vm["freq"].as(); - if (freq > 0) - ctx->settings[ctx->id("target_freq")] = std::to_string(freq * 1e6); - } - - if (vm.count("no-tmdriv")) - ctx->settings[ctx->id("timing_driven")] = false; - - if (vm.count("placer-heap-alpha")) - ctx->settings[ctx->id("placerHeap/alpha")] = std::to_string(vm["placer-heap-alpha"].as()); - - if (vm.count("placer-heap-beta")) - ctx->settings[ctx->id("placerHeap/beta")] = std::to_string(vm["placer-heap-beta"].as()); - - if (vm.count("placer-heap-critexp")) - ctx->settings[ctx->id("placerHeap/criticalityExponent")] = std::to_string(vm["placer-heap-critexp"].as()); - - if (vm.count("placer-heap-timingweight")) - ctx->settings[ctx->id("placerHeap/timingWeight")] = std::to_string(vm["placer-heap-timingweight"].as()); - - if (vm.count("parallel-refine")) - ctx->settings[ctx->id("placerHeap/parallelRefine")] = true; - - if (vm.count("router2-heatmap")) - ctx->settings[ctx->id("router2/heatmap")] = vm["router2-heatmap"].as(); - if (vm.count("tmg-ripup") || vm.count("router2-tmg-ripup")) - ctx->settings[ctx->id("router/tmg_ripup")] = true; - - // Setting default values - if (ctx->settings.find(ctx->id("target_freq")) == ctx->settings.end()) - ctx->settings[ctx->id("target_freq")] = std::to_string(12e6); - if (ctx->settings.find(ctx->id("timing_driven")) == ctx->settings.end()) - ctx->settings[ctx->id("timing_driven")] = true; - if (ctx->settings.find(ctx->id("slack_redist_iter")) == ctx->settings.end()) - ctx->settings[ctx->id("slack_redist_iter")] = 0; - if (ctx->settings.find(ctx->id("auto_freq")) == ctx->settings.end()) - ctx->settings[ctx->id("auto_freq")] = false; - if (ctx->settings.find(ctx->id("placer")) == ctx->settings.end()) - ctx->settings[ctx->id("placer")] = Arch::defaultPlacer; - if (ctx->settings.find(ctx->id("router")) == ctx->settings.end()) - ctx->settings[ctx->id("router")] = Arch::defaultRouter; - - ctx->settings[ctx->id("arch.name")] = std::string(ctx->archId().c_str(ctx)); - ctx->settings[ctx->id("arch.type")] = std::string(ctx->archArgsToId(ctx->archArgs()).c_str(ctx)); - ctx->settings[ctx->id("seed")] = ctx->rngstate; - - if (ctx->settings.find(ctx->id("placerHeap/alpha")) == ctx->settings.end()) - ctx->settings[ctx->id("placerHeap/alpha")] = std::to_string(0.1); - if (ctx->settings.find(ctx->id("placerHeap/beta")) == ctx->settings.end()) - ctx->settings[ctx->id("placerHeap/beta")] = std::to_string(0.9); - if (ctx->settings.find(ctx->id("placerHeap/criticalityExponent")) == ctx->settings.end()) - ctx->settings[ctx->id("placerHeap/criticalityExponent")] = std::to_string(2); - if (ctx->settings.find(ctx->id("placerHeap/timingWeight")) == ctx->settings.end()) - ctx->settings[ctx->id("placerHeap/timingWeight")] = std::to_string(10); - - if (vm.count("detailed-timing-report")) { - ctx->detailed_timing_report = true; - } -} - -int CommandHandler::executeMain(std::unique_ptr ctx) -{ - if (vm.count("on-failure")) { - global_command_handler = this; - std::set_terminate(script_terminate_handler); - } - if (vm.count("test")) { - ctx->archcheck(); - return 0; - } - - if (vm.count("top")) { - ctx->settings[ctx->id("frontend/top")] = vm["top"].as(); - } - -#ifndef NO_GUI - if (vm.count("gui")) { - Application a(argc, argv, (vm.count("gui-no-aa") > 0)); - MainWindow w(std::move(ctx), this); - try { - if (vm.count("json")) { - std::string filename = vm["json"].as(); - std::ifstream f(filename); - if (!parse_json(f, filename, w.getContext())) - log_error("Loading design failed.\n"); - customAfterLoad(w.getContext()); - w.notifyChangeContext(); - w.updateActions(); - } else - w.notifyChangeContext(); - } catch (log_execution_error_exception) { - // show error is handled by gui itself - } - w.show(); - - return a.exec(); - } -#endif - if (vm.count("json")) { - std::string filename = vm["json"].as(); - std::ifstream f(filename); - if (!parse_json(f, filename, ctx.get())) - log_error("Loading design failed.\n"); - - customAfterLoad(ctx.get()); - } - -#ifndef NO_PYTHON - init_python(argv[0]); - python_export_global("ctx", *ctx); - - if (vm.count("run")) { - - std::vector files = vm["run"].as>(); - for (auto filename : files) - execute_python_file(filename.c_str()); - } else -#endif - if (ctx->design_loaded) { - bool do_pack = vm.count("pack-only") != 0 || vm.count("no-pack") == 0; - bool do_place = vm.count("pack-only") == 0 && vm.count("no-place") == 0; - bool do_route = vm.count("pack-only") == 0 && vm.count("no-route") == 0; - - if (do_pack) { - run_script_hook("pre-pack"); - if (!ctx->pack() && !ctx->force) - log_error("Packing design failed.\n"); - } - assign_budget(ctx.get()); - ctx->check(); - print_utilisation(ctx.get()); - - if (do_place) { - run_script_hook("pre-place"); - bool saved_debug = ctx->debug; - if (vm.count("debug-placer")) - ctx->debug = true; - if (!ctx->place() && !ctx->force) - log_error("Placing design failed.\n"); - ctx->debug = saved_debug; - ctx->check(); - if (vm.count("placed-svg")) - ctx->writeSVG(vm["placed-svg"].as(), "scale=50 hide_routing"); - } - - if (do_route) { - run_script_hook("pre-route"); - bool saved_debug = ctx->debug; - if (vm.count("debug-router")) - ctx->debug = true; - if (!ctx->route() && !ctx->force) - log_error("Routing design failed.\n"); - ctx->debug = saved_debug; - run_script_hook("post-route"); - if (vm.count("routed-svg")) - ctx->writeSVG(vm["routed-svg"].as(), "scale=500"); - } - - customBitstream(ctx.get()); - } - - if (vm.count("write")) { - std::string filename = vm["write"].as(); - std::ofstream f(filename); - if (!write_json_file(f, filename, ctx.get())) - log_error("Saving design failed.\n"); - } - - if (vm.count("sdf")) { - std::string filename = vm["sdf"].as(); - std::ofstream f(filename); - if (!f) - log_error("Failed to open SDF file '%s' for writing.\n", filename.c_str()); - ctx->writeSDF(f, vm.count("sdf-cvc")); - } - - if (vm.count("report")) { - std::string filename = vm["report"].as(); - std::ofstream f(filename); - if (!f) - log_error("Failed to open report file '%s' for writing.\n", filename.c_str()); - ctx->writeReport(f); - } - -#ifndef NO_PYTHON - deinit_python(); -#endif - - return had_nonfatal_error ? 1 : 0; -} - -void CommandHandler::conflicting_options(const boost::program_options::variables_map &vm, const char *opt1, - const char *opt2) -{ - if (vm.count(opt1) && !vm[opt1].defaulted() && vm.count(opt2) && !vm[opt2].defaulted()) { - std::string msg = "Conflicting options '" + std::string(opt1) + "' and '" + std::string(opt2) + "'."; - log_error("%s\n", msg.c_str()); - } -} - -void CommandHandler::printFooter() -{ - int warning_count = get_or_default(message_count_by_level, LogLevel::WARNING_MSG, 0), - error_count = get_or_default(message_count_by_level, LogLevel::ERROR_MSG, 0); - if (warning_count > 0 || error_count > 0) - log_always("%d warning%s, %d error%s\n", warning_count, warning_count == 1 ? "" : "s", error_count, - error_count == 1 ? "" : "s"); -} - -int CommandHandler::exec() -{ - try { - if (!parseOptions()) - return -1; - - if (executeBeforeContext()) - return 0; - - dict values; - std::unique_ptr ctx = createContext(values); - setupContext(ctx.get()); - setupArchContext(ctx.get()); - int rc = executeMain(std::move(ctx)); - printFooter(); - log_break(); - log_info("Program finished normally.\n"); - return rc; - } catch (log_execution_error_exception) { - printFooter(); - return -1; - } -} - -void CommandHandler::load_json(Context *ctx, std::string filename) -{ - setupContext(ctx); - setupArchContext(ctx); - { - std::ifstream f(filename); - if (!parse_json(f, filename, ctx)) - log_error("Loading design failed.\n"); - } -} - -void CommandHandler::clear() { vm.clear(); } - -void CommandHandler::run_script_hook(const std::string &name) -{ -#ifndef NO_PYTHON - if (vm.count(name)) { - std::vector files = vm[name].as>(); - for (auto filename : files) - execute_python_file(filename.c_str()); - } -#endif -} - -NEXTPNR_NAMESPACE_END diff --git a/common/command.h b/common/command.h deleted file mode 100644 index 6cce8c61..00000000 --- a/common/command.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * 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 COMMAND_H -#define COMMAND_H - -#include -#include -#include "log.h" -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -namespace po = boost::program_options; - -class CommandHandler -{ - public: - CommandHandler(int argc, char **argv); - virtual ~CommandHandler(){}; - - int exec(); - void load_json(Context *ctx, std::string filename); - void clear(); - void run_script_hook(const std::string &name); - - protected: - virtual void setupArchContext(Context *ctx) = 0; - virtual std::unique_ptr createContext(dict &values) = 0; - virtual po::options_description getArchOptions() = 0; - virtual void validate(){}; - virtual void customAfterLoad(Context *ctx){}; - virtual void customBitstream(Context *ctx){}; - void conflicting_options(const boost::program_options::variables_map &vm, const char *opt1, const char *opt2); - - private: - bool parseOptions(); - bool executeBeforeContext(); - void setupContext(Context *ctx); - int executeMain(std::unique_ptr ctx); - po::options_description getGeneralOptions(); - void printFooter(); - - protected: - po::variables_map vm; - - private: - po::options_description options; - po::positional_options_description pos; - int argc; - char **argv; - std::ofstream logfile; -}; - -NEXTPNR_NAMESPACE_END - -#endif // COMMAND_H diff --git a/common/constraints.h b/common/constraints.h deleted file mode 100644 index 65abf12c..00000000 --- a/common/constraints.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021 The SymbiFlow Authors. - * - * 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 CONSTRAINTS_H -#define CONSTRAINTS_H - -#include -#include - -#include "archdefs.h" -#include "exclusive_state_groups.h" -#include "hashlib.h" -#include "idstring.h" -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct Context; - -template struct Constraints -{ - using ConstraintStateType = StateType; - using ConstraintCountType = CountType; - - enum ConstraintType - { - CONSTRAINT_TAG_IMPLIES = 0, - CONSTRAINT_TAG_REQUIRES = 1, - }; - - template struct Constraint - { - virtual std::size_t tag() const = 0; - virtual ConstraintType constraint_type() const = 0; - virtual StateType state() const = 0; - virtual StateRange states() const = 0; - }; - - typedef ExclusiveStateGroup TagState; - dict> definitions; - - template void bindBel(TagState *tags, const ConstraintRange constraints); - - template void unbindBel(TagState *tags, const ConstraintRange constraints); - - template - bool isValidBelForCellType(const Context *ctx, uint32_t prototype, const TagState *tags, - const ConstraintRange constraints, IdString object, IdString cell, BelId bel, - bool explain_constraints) const; -}; - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/constraints.impl.h b/common/constraints.impl.h deleted file mode 100644 index 9c978411..00000000 --- a/common/constraints.impl.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021 The SymbiFlow Authors. - * - * 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 CONSTRAINTS_IMPL_H -#define CONSTRAINTS_IMPL_H - -#include "exclusive_state_groups.impl.h" - -NEXTPNR_NAMESPACE_BEGIN - -template -template -void Constraints::bindBel(TagState *tags, const ConstraintRange constraints) -{ - for (const auto &constraint : constraints) { - switch (constraint.constraint_type()) { - case CONSTRAINT_TAG_IMPLIES: - tags[constraint.tag()].add_implies(constraint.state()); - break; - case CONSTRAINT_TAG_REQUIRES: - break; - default: - NPNR_ASSERT(false); - } - } -} - -template -template -void Constraints::unbindBel(TagState *tags, const ConstraintRange constraints) -{ - for (const auto &constraint : constraints) { - switch (constraint.constraint_type()) { - case CONSTRAINT_TAG_IMPLIES: - tags[constraint.tag()].remove_implies(constraint.state()); - break; - case CONSTRAINT_TAG_REQUIRES: - break; - default: - NPNR_ASSERT(false); - } - } -} - -template -template -bool Constraints::isValidBelForCellType(const Context *ctx, uint32_t prototype, - const TagState *tags, - const ConstraintRange constraints, - IdString object, IdString cell, BelId bel, - bool explain_constraints) const -{ - if (explain_constraints) { - auto &state_definition = definitions.at(prototype); - for (const auto &constraint : constraints) { - switch (constraint.constraint_type()) { - case CONSTRAINT_TAG_IMPLIES: - tags[constraint.tag()].explain_implies(ctx, object, cell, state_definition.at(constraint.tag()), bel, - constraint.state()); - break; - case CONSTRAINT_TAG_REQUIRES: - tags[constraint.tag()].explain_requires(ctx, object, cell, state_definition.at(constraint.tag()), bel, - constraint.states()); - break; - default: - NPNR_ASSERT(false); - } - } - } - - for (const auto &constraint : constraints) { - switch (constraint.constraint_type()) { - case CONSTRAINT_TAG_IMPLIES: - if (!tags[constraint.tag()].check_implies(constraint.state())) { - return false; - } - break; - case CONSTRAINT_TAG_REQUIRES: - if (!tags[constraint.tag()].requires(constraint.states())) { - return false; - } - break; - default: - NPNR_ASSERT(false); - } - } - - return true; -} - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/context.cc b/common/context.cc deleted file mode 100644 index e35d3e49..00000000 --- a/common/context.cc +++ /dev/null @@ -1,428 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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 "context.h" - -#include "log.h" -#include "nextpnr_namespaces.h" -#include "util.h" - -NEXTPNR_NAMESPACE_BEGIN - -WireId Context::getNetinfoSourceWire(const NetInfo *net_info) const -{ - if (net_info->driver.cell == nullptr) - return WireId(); - - auto src_bel = net_info->driver.cell->bel; - - if (src_bel == BelId()) - return WireId(); - - auto bel_pins = getBelPinsForCellPin(net_info->driver.cell, net_info->driver.port); - auto iter = bel_pins.begin(); - if (iter == bel_pins.end()) - return WireId(); - WireId driver = getBelPinWire(src_bel, *iter); - ++iter; - NPNR_ASSERT(iter == bel_pins.end()); // assert there is only one driver bel pin; - return driver; -} - -SSOArray Context::getNetinfoSinkWires(const NetInfo *net_info, const PortRef &user_info) const -{ - auto dst_bel = user_info.cell->bel; - if (dst_bel == BelId()) - return SSOArray(0, WireId()); - size_t bel_pin_count = 0; - // We use an SSOArray here because it avoids any heap allocation for the 99.9% case of 1 or 2 sink wires - // but as SSOArray doesn't (currently) support resizing to keep things simple it does mean we have to do - // two loops - for (auto s : getBelPinsForCellPin(user_info.cell, user_info.port)) { - (void)s; // unused - ++bel_pin_count; - } - SSOArray result(bel_pin_count, WireId()); - bel_pin_count = 0; - for (auto pin : getBelPinsForCellPin(user_info.cell, user_info.port)) { - result[bel_pin_count++] = getBelPinWire(dst_bel, pin); - } - return result; -} - -size_t Context::getNetinfoSinkWireCount(const NetInfo *net_info, const PortRef &sink) const -{ - size_t count = 0; - for (auto s : getNetinfoSinkWires(net_info, sink)) { - (void)s; // unused - ++count; - } - return count; -} - -WireId Context::getNetinfoSinkWire(const NetInfo *net_info, const PortRef &sink, size_t phys_idx) const -{ - size_t count = 0; - for (auto s : getNetinfoSinkWires(net_info, sink)) { - if (count == phys_idx) - return s; - ++count; - } - /* TODO: This should be an assertion failure, but for the zero-wire case of unplaced sinks; legacy code currently - assumes WireId Remove once the refactoring process is complete. - */ - return WireId(); -} - -delay_t Context::predictArcDelay(const NetInfo *net_info, const PortRef &sink) const -{ - if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId() || sink.cell->bel == BelId()) - return 0; - IdString driver_pin, sink_pin; - // Pick the first pin for a prediction; assume all will be similar enouhg - for (auto pin : getBelPinsForCellPin(net_info->driver.cell, net_info->driver.port)) { - driver_pin = pin; - break; - } - for (auto pin : getBelPinsForCellPin(sink.cell, sink.port)) { - sink_pin = pin; - break; - } - if (driver_pin == IdString() || sink_pin == IdString()) - return 0; - return predictDelay(net_info->driver.cell->bel, driver_pin, sink.cell->bel, sink_pin); -} - -delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &user_info) const -{ -#ifdef ARCH_ECP5 - if (net_info->is_global) - return 0; -#endif - - if (net_info->wires.empty()) - return predictArcDelay(net_info, user_info); - - WireId src_wire = getNetinfoSourceWire(net_info); - if (src_wire == WireId()) - return 0; - - delay_t max_delay = 0; - - for (auto dst_wire : getNetinfoSinkWires(net_info, user_info)) { - WireId cursor = dst_wire; - delay_t delay = 0; - - while (cursor != WireId() && cursor != src_wire) { - auto it = net_info->wires.find(cursor); - - if (it == net_info->wires.end()) - break; - - PipId pip = it->second.pip; - if (pip == PipId()) - break; - - delay += getPipDelay(pip).maxDelay(); - delay += getWireDelay(cursor).maxDelay(); - cursor = getPipSrcWire(pip); - } - - if (cursor == src_wire) - max_delay = std::max(max_delay, delay + getWireDelay(src_wire).maxDelay()); // routed - else - max_delay = std::max(max_delay, predictArcDelay(net_info, user_info)); // unrouted - } - return max_delay; -} - -static uint32_t xorshift32(uint32_t x) -{ - x ^= x << 13; - x ^= x >> 17; - x ^= x << 5; - return x; -} - -uint32_t Context::checksum() const -{ - uint32_t cksum = xorshift32(123456789); - - uint32_t cksum_nets_sum = 0; - for (auto &it : nets) { - auto &ni = *it.second; - uint32_t x = 123456789; - x = xorshift32(x + xorshift32(it.first.index)); - x = xorshift32(x + xorshift32(ni.name.index)); - if (ni.driver.cell) - x = xorshift32(x + xorshift32(ni.driver.cell->name.index)); - x = xorshift32(x + xorshift32(ni.driver.port.index)); - x = xorshift32(x + xorshift32(getDelayChecksum(ni.driver.budget))); - - for (auto &u : ni.users) { - if (u.cell) - x = xorshift32(x + xorshift32(u.cell->name.index)); - x = xorshift32(x + xorshift32(u.port.index)); - x = xorshift32(x + xorshift32(getDelayChecksum(u.budget))); - } - - uint32_t attr_x_sum = 0; - for (auto &a : ni.attrs) { - uint32_t attr_x = 123456789; - attr_x = xorshift32(attr_x + xorshift32(a.first.index)); - for (char ch : a.second.str) - attr_x = xorshift32(attr_x + xorshift32((int)ch)); - attr_x_sum += attr_x; - } - x = xorshift32(x + xorshift32(attr_x_sum)); - - uint32_t wire_x_sum = 0; - for (auto &w : ni.wires) { - uint32_t wire_x = 123456789; - wire_x = xorshift32(wire_x + xorshift32(getWireChecksum(w.first))); - wire_x = xorshift32(wire_x + xorshift32(getPipChecksum(w.second.pip))); - wire_x = xorshift32(wire_x + xorshift32(int(w.second.strength))); - wire_x_sum += wire_x; - } - x = xorshift32(x + xorshift32(wire_x_sum)); - - cksum_nets_sum += x; - } - cksum = xorshift32(cksum + xorshift32(cksum_nets_sum)); - - uint32_t cksum_cells_sum = 0; - for (auto &it : cells) { - auto &ci = *it.second; - uint32_t x = 123456789; - x = xorshift32(x + xorshift32(it.first.index)); - x = xorshift32(x + xorshift32(ci.name.index)); - x = xorshift32(x + xorshift32(ci.type.index)); - - uint32_t port_x_sum = 0; - for (auto &p : ci.ports) { - uint32_t port_x = 123456789; - port_x = xorshift32(port_x + xorshift32(p.first.index)); - port_x = xorshift32(port_x + xorshift32(p.second.name.index)); - if (p.second.net) - port_x = xorshift32(port_x + xorshift32(p.second.net->name.index)); - port_x = xorshift32(port_x + xorshift32(p.second.type)); - port_x_sum += port_x; - } - x = xorshift32(x + xorshift32(port_x_sum)); - - uint32_t attr_x_sum = 0; - for (auto &a : ci.attrs) { - uint32_t attr_x = 123456789; - attr_x = xorshift32(attr_x + xorshift32(a.first.index)); - for (char ch : a.second.str) - attr_x = xorshift32(attr_x + xorshift32((int)ch)); - attr_x_sum += attr_x; - } - x = xorshift32(x + xorshift32(attr_x_sum)); - - uint32_t param_x_sum = 0; - for (auto &p : ci.params) { - uint32_t param_x = 123456789; - param_x = xorshift32(param_x + xorshift32(p.first.index)); - for (char ch : p.second.str) - param_x = xorshift32(param_x + xorshift32((int)ch)); - param_x_sum += param_x; - } - x = xorshift32(x + xorshift32(param_x_sum)); - - x = xorshift32(x + xorshift32(getBelChecksum(ci.bel))); - x = xorshift32(x + xorshift32(ci.belStrength)); - - cksum_cells_sum += x; - } - cksum = xorshift32(cksum + xorshift32(cksum_cells_sum)); - - return cksum; -} - -void Context::check() const -{ - bool check_failed = false; - -#define CHECK_FAIL(...) \ - do { \ - log_nonfatal_error(__VA_ARGS__); \ - check_failed = true; \ - } while (false) - - for (auto &n : nets) { - auto ni = n.second.get(); - if (n.first != ni->name) - CHECK_FAIL("net key '%s' not equal to name '%s'\n", nameOf(n.first), nameOf(ni->name)); - for (auto &w : ni->wires) { - if (ni != getBoundWireNet(w.first)) - CHECK_FAIL("net '%s' not bound to wire '%s' in wires map\n", nameOf(n.first), nameOfWire(w.first)); - if (w.second.pip != PipId()) { - if (w.first != getPipDstWire(w.second.pip)) - CHECK_FAIL("net '%s' has dest mismatch '%s' vs '%s' in for pip '%s'\n", nameOf(n.first), - nameOfWire(w.first), nameOfWire(getPipDstWire(w.second.pip)), nameOfPip(w.second.pip)); - if (ni != getBoundPipNet(w.second.pip)) - CHECK_FAIL("net '%s' not bound to pip '%s' in wires map\n", nameOf(n.first), - nameOfPip(w.second.pip)); - } - } - if (ni->driver.cell != nullptr) { - if (!ni->driver.cell->ports.count(ni->driver.port)) { - CHECK_FAIL("net '%s' driver port '%s' missing on cell '%s'\n", nameOf(n.first), nameOf(ni->driver.port), - nameOf(ni->driver.cell)); - } else { - const NetInfo *p_net = ni->driver.cell->ports.at(ni->driver.port).net; - if (p_net != ni) - CHECK_FAIL("net '%s' driver port '%s.%s' connected to incorrect net '%s'\n", nameOf(n.first), - nameOf(ni->driver.cell), nameOf(ni->driver.port), p_net ? nameOf(p_net) : ""); - } - } - for (auto user : ni->users) { - if (!user.cell->ports.count(user.port)) { - CHECK_FAIL("net '%s' user port '%s' missing on cell '%s'\n", nameOf(n.first), nameOf(user.port), - nameOf(user.cell)); - } else { - const NetInfo *p_net = user.cell->ports.at(user.port).net; - if (p_net != ni) - CHECK_FAIL("net '%s' user port '%s.%s' connected to incorrect net '%s'\n", nameOf(n.first), - nameOf(user.cell), nameOf(user.port), p_net ? nameOf(p_net) : ""); - } - } - } -#ifdef CHECK_WIRES - for (auto w : getWires()) { - auto ni = getBoundWireNet(w); - if (ni != nullptr) { - if (!ni->wires.count(w)) - CHECK_FAIL("wire '%s' missing in wires map of bound net '%s'\n", nameOfWire(w), nameOf(ni)); - } - } -#endif - for (auto &c : cells) { - auto ci = c.second.get(); - if (c.first != ci->name) - CHECK_FAIL("cell key '%s' not equal to name '%s'\n", nameOf(c.first), nameOf(ci->name)); - if (ci->bel != BelId()) { - if (getBoundBelCell(c.second->bel) != ci) - CHECK_FAIL("cell '%s' not bound to bel '%s' in bel field\n", nameOf(c.first), nameOfBel(ci->bel)); - } - for (auto &port : c.second->ports) { - NetInfo *net = port.second.net; - if (net != nullptr) { - if (nets.find(net->name) == nets.end()) { - CHECK_FAIL("cell port '%s.%s' connected to non-existent net '%s'\n", nameOf(c.first), - nameOf(port.first), nameOf(net->name)); - } else if (port.second.type == PORT_OUT) { - if (net->driver.cell != c.second.get() || net->driver.port != port.first) { - CHECK_FAIL("output cell port '%s.%s' not in driver field of net '%s'\n", nameOf(c.first), - nameOf(port.first), nameOf(net)); - } - } else if (port.second.type == PORT_IN) { - if (!port.second.user_idx) - CHECK_FAIL("input cell port '%s.%s' on net '%s' has no user index\n", nameOf(c.first), - nameOf(port.first), nameOf(net)); - auto net_user = net->users.at(port.second.user_idx); - if (net_user.cell != c.second.get() || net_user.port != port.first) - CHECK_FAIL("input cell port '%s.%s' not in associated user entry of net '%s'\n", - nameOf(c.first), nameOf(port.first), nameOf(net)); - } - } - } - } - -#undef CHECK_FAIL - - if (check_failed) - log_error("INTERNAL CHECK FAILED: please report this error with the design and full log output. Failure " - "details are above this message.\n"); -} - -namespace { -struct FixupHierarchyWorker -{ - FixupHierarchyWorker(Context *ctx) : ctx(ctx){}; - Context *ctx; - void run() - { - trim_hierarchy(ctx->top_module); - rebuild_hierarchy(); - }; - // Remove cells and nets that no longer exist in the netlist - std::vector todelete_cells, todelete_nets; - void trim_hierarchy(IdString path) - { - auto &h = ctx->hierarchy.at(path); - todelete_cells.clear(); - todelete_nets.clear(); - for (auto &lc : h.leaf_cells) { - if (!ctx->cells.count(lc.second)) - todelete_cells.push_back(lc.first); - } - for (auto &n : h.nets) - if (!ctx->nets.count(n.second)) - todelete_nets.push_back(n.first); - for (auto tdc : todelete_cells) { - h.leaf_cells_by_gname.erase(h.leaf_cells.at(tdc)); - h.leaf_cells.erase(tdc); - } - for (auto tdn : todelete_nets) { - h.nets_by_gname.erase(h.nets.at(tdn)); - h.nets.erase(tdn); - } - for (auto &sc : h.hier_cells) - trim_hierarchy(sc.second); - } - - IdString construct_local_name(HierarchicalCell &hc, IdString global_name, bool is_cell) - { - std::string gn = global_name.str(ctx); - auto dp = gn.find_last_of('.'); - if (dp != std::string::npos) - gn = gn.substr(dp + 1); - IdString name = ctx->id(gn); - // Make sure name is unique - int adder = 0; - while (is_cell ? hc.leaf_cells.count(name) : hc.nets.count(name)) { - ++adder; - name = ctx->id(gn + "$" + std::to_string(adder)); - } - return name; - } - - // Update hierarchy structure for nets and cells that have hiercell set - void rebuild_hierarchy() - { - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (ci->hierpath == IdString()) - ci->hierpath = ctx->top_module; - auto &hc = ctx->hierarchy.at(ci->hierpath); - if (hc.leaf_cells_by_gname.count(ci->name)) - continue; // already known - IdString local_name = construct_local_name(hc, ci->name, true); - hc.leaf_cells_by_gname[ci->name] = local_name; - hc.leaf_cells[local_name] = ci->name; - } - } -}; -} // namespace - -void Context::fixupHierarchy() { FixupHierarchyWorker(this).run(); } - -NEXTPNR_NAMESPACE_END diff --git a/common/context.h b/common/context.h deleted file mode 100644 index cb8fd257..00000000 --- a/common/context.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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 CONTEXT_H -#define CONTEXT_H - -#include - -#include "arch.h" -#include "deterministic_rng.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct Context : Arch, DeterministicRNG -{ - bool verbose = false; - bool debug = false; - bool force = false; - - // Should we disable printing of the location of nets in the critical path? - bool disable_critical_path_source_print = false; - // True when detailed per-net timing is to be stored / reported - bool detailed_timing_report = false; - - ArchArgs arch_args; - - Context(ArchArgs args) : Arch(args) - { - BaseCtx::as_ctx = this; - arch_args = args; - } - - ArchArgs getArchArgs() { return arch_args; } - - // -------------------------------------------------------------- - - delay_t predictArcDelay(const NetInfo *net_info, const PortRef &sink) const; - - WireId getNetinfoSourceWire(const NetInfo *net_info) const; - SSOArray getNetinfoSinkWires(const NetInfo *net_info, const PortRef &sink) const; - size_t getNetinfoSinkWireCount(const NetInfo *net_info, const PortRef &sink) const; - WireId getNetinfoSinkWire(const NetInfo *net_info, const PortRef &sink, size_t phys_idx) const; - delay_t getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &sink) const; - - // provided by router1.cc - bool checkRoutedDesign() const; - bool getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t *delay = nullptr, - dict *route = nullptr, bool useEstimate = true); - - // -------------------------------------------------------------- - // call after changing hierpath or adding/removing nets and cells - void fixupHierarchy(); - - // -------------------------------------------------------------- - - // provided by sdf.cc - void writeSDF(std::ostream &out, bool cvc_mode = false) const; - - // -------------------------------------------------------------- - - // provided by svg.cc - void writeSVG(const std::string &filename, const std::string &flags = "") const; - - // -------------------------------------------------------------- - - // provided by report.cc - void writeReport(std::ostream &out) const; - // -------------------------------------------------------------- - - uint32_t checksum() const; - - void check() const; - void archcheck() const; - - template T setting(const char *name, T defaultValue) - { - IdString new_id = id(name); - auto found = settings.find(new_id); - if (found != settings.end()) - return boost::lexical_cast(found->second.is_string ? found->second.as_string() - : std::to_string(found->second.as_int64())); - else - settings[id(name)] = std::to_string(defaultValue); - - return defaultValue; - } - - template T setting(const char *name) const - { - IdString new_id = id(name); - auto found = settings.find(new_id); - if (found != settings.end()) - return boost::lexical_cast(found->second.is_string ? found->second.as_string() - : std::to_string(found->second.as_int64())); - else - throw std::runtime_error("settings does not exists"); - } -}; - -NEXTPNR_NAMESPACE_END - -#endif /* CONTEXT_H */ diff --git a/common/design_utils.cc b/common/design_utils.cc deleted file mode 100644 index f52cc304..00000000 --- a/common/design_utils.cc +++ /dev/null @@ -1,52 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * 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. - * - */ - -#include "design_utils.h" -#include -#include -#include "log.h" -#include "util.h" -NEXTPNR_NAMESPACE_BEGIN - -// Print utilisation of a design -void print_utilisation(const Context *ctx) -{ - // Sort by Bel type - std::map used_types; - for (auto &cell : ctx->cells) { - used_types[ctx->getBelBucketName(ctx->getBelBucketForCellType(cell.second.get()->type))]++; - } - std::map available_types; - for (auto bel : ctx->getBels()) { - if (!ctx->getBelHidden(bel)) { - available_types[ctx->getBelBucketName(ctx->getBelBucketForBel(bel))]++; - } - } - log_break(); - log_info("Device utilisation:\n"); - for (auto type : available_types) { - IdString type_id = type.first; - int used_bels = get_or_default(used_types, type.first, 0); - log_info("\t%20s: %5d/%5d %5d%%\n", type_id.c_str(ctx), used_bels, type.second, 100 * used_bels / type.second); - } - log_break(); -} - -NEXTPNR_NAMESPACE_END diff --git a/common/design_utils.h b/common/design_utils.h deleted file mode 100644 index 069600b5..00000000 --- a/common/design_utils.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * 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. - * - */ - -#include "nextpnr.h" - -#ifndef DESIGN_UTILS_H -#define DESIGN_UTILS_H - -#include - -NEXTPNR_NAMESPACE_BEGIN - -/* -Utilities for design manipulation, intended for use inside packing algorithms - */ - -// Disconnect a net (if connected) from old, and connect it to rep -void replace_port(CellInfo *old_cell, IdString old_name, CellInfo *rep_cell, IdString rep_name); - -// If a net drives a given port of a cell matching a predicate (in many -// cases more than one cell type, e.g. SB_DFFxx so a predicate is used), return -// the first instance of that cell (otherwise nullptr). If exclusive is set to -// true, then this cell must be the only load. If ignore_cell is set, that cell -// is not considered -template -CellInfo *net_only_drives(const Context *ctx, NetInfo *net, F1 cell_pred, IdString port, bool exclusive = false, - CellInfo *exclude = nullptr) -{ - if (net == nullptr) - return nullptr; - if (exclusive) { - if (exclude == nullptr) { - if (net->users.entries() != 1) - return nullptr; - } else { - if (net->users.entries() > 2) { - return nullptr; - } else if (net->users.entries() == 2) { - bool found = false; - for (auto &usr : net->users) { - if (usr.cell == exclude) - found = true; - } - if (!found) - return nullptr; - } - } - } - for (const auto &load : net->users) { - if (load.cell != exclude && cell_pred(ctx, load.cell) && load.port == port) { - return load.cell; - } - } - return nullptr; -} - -// If a net is driven by a given port of a cell matching a predicate, return -// that cell, otherwise nullptr -template CellInfo *net_driven_by(const Context *ctx, const NetInfo *net, F1 cell_pred, IdString port) -{ - if (net == nullptr) - return nullptr; - if (net->driver.cell == nullptr) - return nullptr; - if (cell_pred(ctx, net->driver.cell) && net->driver.port == port) { - return net->driver.cell; - } else { - return nullptr; - } -} - -// Check if a port is used -inline bool port_used(CellInfo *cell, IdString port_name) -{ - auto port_fnd = cell->ports.find(port_name); - return port_fnd != cell->ports.end() && port_fnd->second.net != nullptr; -} - -void print_utilisation(const Context *ctx); - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/deterministic_rng.h b/common/deterministic_rng.h deleted file mode 100644 index 3aab5a49..00000000 --- a/common/deterministic_rng.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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 DETERMINISTIC_RNG_H -#define DETERMINISTIC_RNG_H - -#include -#include -#include -#include - -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct DeterministicRNG -{ - uint64_t rngstate; - - DeterministicRNG() : rngstate(0x3141592653589793) {} - - uint64_t rng64() - { - // xorshift64star - // https://arxiv.org/abs/1402.6246 - - uint64_t retval = rngstate * 0x2545F4914F6CDD1D; - - rngstate ^= rngstate >> 12; - rngstate ^= rngstate << 25; - rngstate ^= rngstate >> 27; - - return retval; - } - - int rng() { return rng64() & 0x3fffffff; } - - int rng(int n) - { - assert(n > 0); - - // round up to power of 2 - int m = n - 1; - m |= (m >> 1); - m |= (m >> 2); - m |= (m >> 4); - m |= (m >> 8); - m |= (m >> 16); - m += 1; - - while (1) { - int x = rng64() & (m - 1); - if (x < n) - return x; - } - } - - void rngseed(uint64_t seed) - { - rngstate = seed ? seed : 0x3141592653589793; - for (int i = 0; i < 5; i++) - rng64(); - } - - template void shuffle(const Iter &begin, const Iter &end) - { - std::size_t size = end - begin; - for (std::size_t i = 0; i != size; i++) { - std::size_t j = i + rng(size - i); - if (j > i) - std::swap(*(begin + i), *(begin + j)); - } - } - - template void shuffle(std::vector &a) { shuffle(a.begin(), a.end()); } - - template void sorted_shuffle(std::vector &a) - { - std::sort(a.begin(), a.end()); - shuffle(a); - } -}; - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/dynamic_bitarray.h b/common/dynamic_bitarray.h deleted file mode 100644 index be41835b..00000000 --- a/common/dynamic_bitarray.h +++ /dev/null @@ -1,211 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021 Symbiflow Authors - * - * 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 DYNAMIC_BITARRAY_H -#define DYNAMIC_BITARRAY_H - -#include -#include -#include - -#include "log.h" -#include "nextpnr_assertions.h" -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -// This class implements a simple dynamic bitarray, backed by some resizable -// random access storage. The default is to use a std::vector. -template > class DynamicBitarray -{ - public: - static_assert(!std::numeric_limits::is_signed, "Storage must be unsigned!"); - - void fill(bool value) - { - std::fill(storage.begin(), storage.end(), value ? std::numeric_limits::max() : 0); - } - - constexpr size_t bits_per_value() const { return std::numeric_limits::digits; } - - bool get(size_t bit) const - { - size_t element_index = bit / bits_per_value(); - size_t bit_offset = bit % bits_per_value(); - - auto element = storage.at(element_index); - return (element & (1 << bit_offset)) != 0; - } - - void set(size_t bit, bool value) - { - size_t element_index = bit / bits_per_value(); - size_t bit_offset = bit % bits_per_value(); - - if (value) { - storage.at(element_index) |= (1 << bit_offset); - } else { - storage.at(element_index) &= ~(1 << bit_offset); - } - } - - void resize(size_t number_bits) - { - size_t required_storage = (number_bits + bits_per_value() - 1) / bits_per_value(); - storage.resize(required_storage); - } - - size_t size() const { return storage.size() * bits_per_value(); } - - void clear() { return storage.clear(); } - - // Convert IntType to a DynamicBitarray of sufficent width - template static DynamicBitarray to_bitarray(const IntType &value) - { - if (std::numeric_limits::is_signed) { - if (value < 0) { - log_error("Expected position value, got %s\n", std::to_string(value).c_str()); - } - } - - DynamicBitarray result; - result.resize(std::numeric_limits::digits); - result.fill(false); - - // Use a 1 of the right type (for shifting) - IntType one(1); - - for (size_t i = 0; i < std::numeric_limits::digits; ++i) { - if ((value & (one << i)) != 0) { - result.set(i, true); - } - } - - return result; - } - - // Convert binary bitstring to a DynamicBitarray of sufficent width - // - // string must be satisfy the following regex: - // - // [01]+ - // - // width can either be specified explicitly, or -1 to use a size wide - // enough to store the given string. - // - // If the width is specified and the width is insufficent it will result - // in an error. - static DynamicBitarray parse_binary_bitstring(int width, const std::string &bits) - { - NPNR_ASSERT(width == -1 || width > 0); - - DynamicBitarray result; - // If no width was supplied, use the width from the input data. - if (width == -1) { - width = bits.size(); - } - - NPNR_ASSERT(width >= 0); - if ((size_t)width < bits.size()) { - log_error("String '%s' is wider than specified width %d\n", bits.c_str(), width); - } - result.resize(width); - result.fill(false); - - for (size_t i = 0; i < bits.size(); ++i) { - // bits[0] is the MSB! - size_t index = width - 1 - i; - if (!(bits[i] == '1' || bits[i] == '0')) { - log_error("String '%s' is not a valid binary bitstring?\n", bits.c_str()); - } - result.set(index, bits[i] == '1'); - } - - return result; - } - - // Convert hex bitstring to a DynamicBitarray of sufficent width - // - // string must be satisfy the following regex: - // - // [0-9a-fA-F]+ - // - // width can either be specified explicitly, or -1 to use a size wide - // enough to store the given string. - // - // If the width is specified and the width is insufficent it will result - // in an error. - static DynamicBitarray parse_hex_bitstring(int width, const std::string &bits) - { - NPNR_ASSERT(width == -1 || width > 0); - - DynamicBitarray result; - // If no width was supplied, use the width from the input data. - if (width == -1) { - // Each character is 4 bits! - width = bits.size() * 4; - } - - NPNR_ASSERT(width >= 0); - int rem = width % 4; - size_t check_width = width; - if (rem != 0) { - check_width += (4 - rem); - } - if (check_width < bits.size() * 4) { - log_error("String '%s' is wider than specified width %d (check_width = %zu)\n", bits.c_str(), width, - check_width); - } - - result.resize(width); - result.fill(false); - - size_t index = 0; - for (auto nibble_iter = bits.rbegin(); nibble_iter != bits.rend(); ++nibble_iter) { - char nibble = *nibble_iter; - - int value; - if (nibble >= '0' && nibble <= '9') { - value = nibble - '0'; - } else if (nibble >= 'a' && nibble <= 'f') { - value = 10 + (nibble - 'a'); - } else if (nibble >= 'A' && nibble <= 'F') { - value = 10 + (nibble - 'A'); - } else { - log_error("Invalid hex string '%s'?\n", bits.c_str()); - } - NPNR_ASSERT(value >= 0); - NPNR_ASSERT(value < 16); - - // Insert nibble into bitarray. - for (size_t i = 0; i < 4; ++i) { - result.set(index++, (value & (1 << i)) != 0); - } - } - - return result; - } - - private: - Storage storage; -}; - -NEXTPNR_NAMESPACE_END - -#endif /* DYNAMIC_BITARRAY_H */ diff --git a/common/embed.cc b/common/embed.cc deleted file mode 100644 index 70bbc6fb..00000000 --- a/common/embed.cc +++ /dev/null @@ -1,49 +0,0 @@ -#if defined(WIN32) -#include -#endif -#include -#include -#include "embed.h" -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -#if defined(EXTERNAL_CHIPDB_ROOT) - -const void *get_chipdb(const std::string &filename) -{ - static std::map files; - if (!files.count(filename)) { - std::string full_filename = EXTERNAL_CHIPDB_ROOT "/" + filename; - if (boost::filesystem::exists(full_filename)) - files[filename].open(full_filename, boost::iostreams::mapped_file::priv); - } - if (files.count(filename)) - return files.at(filename).data(); - return nullptr; -} - -#elif defined(WIN32) - -const void *get_chipdb(const std::string &filename) -{ - HRSRC rc = ::FindResource(nullptr, filename.c_str(), RT_RCDATA); - HGLOBAL rcData = ::LoadResource(nullptr, rc); - return ::LockResource(rcData); -} - -#else - -EmbeddedFile *EmbeddedFile::head = nullptr; - -const void *get_chipdb(const std::string &filename) -{ - for (EmbeddedFile *file = EmbeddedFile::head; file; file = file->next) - if (file->filename == filename) - return file->content; - return nullptr; -} - -#endif - -NEXTPNR_NAMESPACE_END diff --git a/common/embed.h b/common/embed.h deleted file mode 100644 index 5f2754f8..00000000 --- a/common/embed.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2020 whitequark - * - * 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 EMBED_H -#define EMBED_H - -#include "nextpnr.h" -NEXTPNR_NAMESPACE_BEGIN - -#if !defined(EXTERNAL_CHIPDB_ROOT) && !defined(WIN32) - -struct EmbeddedFile -{ - static EmbeddedFile *head; - - std::string filename; - const void *content; - EmbeddedFile *next = nullptr; - - EmbeddedFile(const std::string &filename, const void *content) : filename(filename), content(content) - { - next = head; - head = this; - } -}; - -#endif - -const void *get_chipdb(const std::string &filename); - -NEXTPNR_NAMESPACE_END - -#endif // EMBED_H diff --git a/common/exclusive_state_groups.h b/common/exclusive_state_groups.h deleted file mode 100644 index 68ce7c4e..00000000 --- a/common/exclusive_state_groups.h +++ /dev/null @@ -1,154 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021 The SymbiFlow Authors. - * - * 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 EXCLUSIVE_STATE_GROUPS_H -#define EXCLUSIVE_STATE_GROUPS_H - -#include -#include -#include -#include -#include - -#include "archdefs.h" -#include "bits.h" -#include "idstring.h" -#include "nextpnr_assertions.h" -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -// Implementation for exclusive state groups, used to implement generic -// constraint system. -template struct ExclusiveStateGroup -{ - ExclusiveStateGroup() : state(kNoSelected) { count.fill(0); } - struct Definition - { - IdString prefix; - IdString default_state; - std::vector states; - }; - - static_assert(StateCount < std::numeric_limits::max(), "StateType cannot store max StateType"); - static_assert(std::numeric_limits::is_signed, "StateType must be signed"); - - std::bitset selected_states; - StateType state; - std::array count; - - static constexpr StateType kNoSelected = -1; - static constexpr StateType kOverConstrained = -2; - - std::pair current_state(const Definition &definition) const - { - if (state <= 0) { - return std::make_pair(state == kNoSelected, definition.default_state); - } else { - NPNR_ASSERT(state <= definition.states.size()); - return std::make_pair(true, definition.states[state]); - } - } - - bool check_implies(int32_t next_state) const - { - // Implies can be satified if either that state is - // selected, or no state is currently selected. - return state == next_state || state == kNoSelected; - } - - bool add_implies(int32_t next_state) - { - NPNR_ASSERT(next_state >= 0 && (size_t)next_state < StateCount); - - // Increment and mark the state as selected. - count[next_state] += 1; - selected_states[next_state] = true; - - if (state == next_state) { - // State was already selected, state group is still satified. - return true; - } else if (selected_states.count() == 1) { - // State was not select selected, state is now selected. - // State group is satified. - state = next_state; - return true; - } else { - // State group is now overconstrained. - state = kOverConstrained; - return false; - } - }; - - void remove_implies(int32_t next_state) - { - NPNR_ASSERT(next_state >= 0 && (size_t)next_state < StateCount); - NPNR_ASSERT(selected_states[next_state]); - - count[next_state] -= 1; - NPNR_ASSERT(count[next_state] >= 0); - - // Check if next_state is now unselected. - if (count[next_state] == 0) { - // next_state is not longer selected - selected_states[next_state] = false; - - // Check whether the state group is now unselected or satified. - auto value = selected_states.to_ulong(); - auto number_selected = Bits::popcount(value); - if (number_selected == 1) { - // Group is no longer overconstrained. - state = Bits::ctz(value); - NPNR_ASSERT(selected_states[state]); - } else if (number_selected == 0) { - // Group is unselected. - state = kNoSelected; - } else { - state = kOverConstrained; - } - } - } - - template bool requires(const StateRange &state_range) const - { - if (state < 0) { - return false; - } - - for (const auto required_state : state_range) { - if (state == required_state) { - return true; - } - } - - return false; - } - - void print_debug(const Context *ctx, IdString object, const Definition &definition) const; - void explain_implies(const Context *ctx, IdString object, IdString cell, const Definition &definition, BelId bel, - int32_t next_state) const; - - template - void explain_requires(const Context *ctx, IdString object, IdString cell, const Definition &definition, BelId bel, - const StateRange state_range) const; -}; - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/exclusive_state_groups.impl.h b/common/exclusive_state_groups.impl.h deleted file mode 100644 index f3ddb5fd..00000000 --- a/common/exclusive_state_groups.impl.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021 The SymbiFlow Authors. - * - * 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. - * - */ - -#pragma once - -#include "context.h" -#include "exclusive_state_groups.h" -#include "log.h" - -NEXTPNR_NAMESPACE_BEGIN - -template -void ExclusiveStateGroup::print_debug(const Context *ctx, IdString object, - const Definition &definition) const -{ - if (state == kNoSelected) { - NPNR_ASSERT(selected_states.count() == 0); - log_info("%s.%s is currently unselected\n", object.c_str(ctx), definition.prefix.c_str(ctx)); - } else if (state >= 0) { - log_info("%s.%s = %s, count = %d\n", object.c_str(ctx), definition.prefix.c_str(ctx), - definition.states.at(state).c_str(ctx), count[state]); - } else { - NPNR_ASSERT(state == kOverConstrained); - log_info("%s.%s is currently overconstrained, states selected:\n", object.c_str(ctx), - definition.prefix.c_str(ctx)); - for (size_t i = 0; i < definition.states.size(); ++i) { - if (selected_states[i]) { - log_info(" - %s, count = %d\n", definition.states.at(i).c_str(ctx), count[i]); - } - } - } -} - -template -void ExclusiveStateGroup::explain_implies(const Context *ctx, IdString object, - IdString cell, const Definition &definition, - BelId bel, int32_t next_state) const -{ - if (check_implies(next_state)) { - log_info("Placing cell %s at bel %s does not violate %s.%s\n", cell.c_str(ctx), ctx->nameOfBel(bel), - object.c_str(ctx), definition.prefix.c_str(ctx)); - } else { - log_info("Placing cell %s at bel %s does violates %s.%s, desired state = %s.\n", cell.c_str(ctx), - ctx->nameOfBel(bel), object.c_str(ctx), definition.prefix.c_str(ctx), - definition.states.at(next_state).c_str(ctx)); - print_debug(ctx, object, definition); - } -} - -template -template -void ExclusiveStateGroup::explain_requires(const Context *ctx, IdString object, - IdString cell, - const Definition &definition, BelId bel, - const StateRange state_range) const -{ - if (requires(state_range)) { - log_info("Placing cell %s at bel %s does not violate %s.%s\n", cell.c_str(ctx), ctx->nameOfBel(bel), - object.c_str(ctx), definition.prefix.c_str(ctx)); - } else { - log_info("Placing cell %s at bel %s does violate %s.%s, because current state is %s, constraint requires one " - "of:\n", - cell.c_str(ctx), ctx->nameOfBel(bel), object.c_str(ctx), definition.prefix.c_str(ctx), - state != -1 ? definition.states.at(state).c_str(ctx) : "unset"); - - for (const auto required_state : state_range) { - log_info(" - %s\n", definition.states.at(required_state).c_str(ctx)); - } - print_debug(ctx, object, definition); - } -} - -NEXTPNR_NAMESPACE_END diff --git a/common/fast_bels.h b/common/fast_bels.h deleted file mode 100644 index ba9938c6..00000000 --- a/common/fast_bels.h +++ /dev/null @@ -1,188 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * 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. - * - */ - -#pragma once - -#include -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -// FastBels is a lookup class that provides a fast lookup for finding BELs -// that support a given cell type. -struct FastBels -{ - struct TypeData - { - size_t type_index; - int number_of_possible_bels; - }; - - FastBels(Context *ctx, bool check_bel_available, int minBelsForGridPick) - : ctx(ctx), check_bel_available(check_bel_available), minBelsForGridPick(minBelsForGridPick) - { - } - - void addCellType(IdString cell_type) - { - auto iter = cell_types.find(cell_type); - if (iter != cell_types.end()) { - // This cell type has already been added to the fast BEL lookup. - return; - } - - size_t type_idx = cell_types.size(); - auto &cell_type_data = cell_types[cell_type]; - cell_type_data.type_index = type_idx; - - fast_bels_by_cell_type.resize(type_idx + 1); - auto &bel_data = fast_bels_by_cell_type.at(type_idx); - NPNR_ASSERT(bel_data.get() == nullptr); - bel_data = std::make_unique(); - - for (auto bel : ctx->getBels()) { - if (!ctx->isValidBelForCellType(cell_type, bel)) { - continue; - } - - cell_type_data.number_of_possible_bels += 1; - } - - for (auto bel : ctx->getBels()) { - if (check_bel_available && !ctx->checkBelAvail(bel)) { - continue; - } - - if (!ctx->isValidBelForCellType(cell_type, bel)) { - continue; - } - - Loc loc = ctx->getBelLocation(bel); - if (minBelsForGridPick >= 0 && cell_type_data.number_of_possible_bels < minBelsForGridPick) { - loc.x = loc.y = 0; - } - - if (int(bel_data->size()) < (loc.x + 1)) { - bel_data->resize(loc.x + 1); - } - - if (int(bel_data->at(loc.x).size()) < (loc.y + 1)) { - bel_data->at(loc.x).resize(loc.y + 1); - } - - bel_data->at(loc.x).at(loc.y).push_back(bel); - } - } - - void addBelBucket(BelBucketId partition) - { - auto iter = partition_types.find(partition); - if (iter != partition_types.end()) { - // This partition has already been added to the fast BEL lookup. - return; - } - - size_t type_idx = partition_types.size(); - auto &type_data = partition_types[partition]; - type_data.type_index = type_idx; - - fast_bels_by_partition_type.resize(type_idx + 1); - auto &bel_data = fast_bels_by_partition_type.at(type_idx); - NPNR_ASSERT(bel_data.get() == nullptr); - bel_data = std::make_unique(); - - for (auto bel : ctx->getBels()) { - if (ctx->getBelBucketForBel(bel) != partition) { - continue; - } - - type_data.number_of_possible_bels += 1; - } - - for (auto bel : ctx->getBels()) { - if (check_bel_available && !ctx->checkBelAvail(bel)) { - continue; - } - - if (ctx->getBelBucketForBel(bel) != partition) { - continue; - } - - Loc loc = ctx->getBelLocation(bel); - if (minBelsForGridPick >= 0 && type_data.number_of_possible_bels < minBelsForGridPick) { - loc.x = loc.y = 0; - } - - if (int(bel_data->size()) < (loc.x + 1)) { - bel_data->resize(loc.x + 1); - } - - if (int(bel_data->at(loc.x).size()) < (loc.y + 1)) { - bel_data->at(loc.x).resize(loc.y + 1); - } - - bel_data->at(loc.x).at(loc.y).push_back(bel); - } - } - - typedef std::vector>> FastBelsData; - - int getBelsForCellType(IdString cell_type, FastBelsData **data) - { - auto iter = cell_types.find(cell_type); - if (iter == cell_types.end()) { - addCellType(cell_type); - iter = cell_types.find(cell_type); - NPNR_ASSERT(iter != cell_types.end()); - } - - auto cell_type_data = iter->second; - - *data = fast_bels_by_cell_type.at(cell_type_data.type_index).get(); - return cell_type_data.number_of_possible_bels; - } - - size_t getBelsForBelBucket(BelBucketId partition, FastBelsData **data) - { - auto iter = partition_types.find(partition); - if (iter == partition_types.end()) { - addBelBucket(partition); - iter = partition_types.find(partition); - NPNR_ASSERT(iter != partition_types.end()); - } - - auto type_data = iter->second; - - *data = fast_bels_by_partition_type.at(type_data.type_index).get(); - return type_data.number_of_possible_bels; - } - - Context *ctx; - const bool check_bel_available; - const int minBelsForGridPick; - - dict cell_types; - std::vector> fast_bels_by_cell_type; - - dict partition_types; - std::vector> fast_bels_by_partition_type; -}; - -NEXTPNR_NAMESPACE_END diff --git a/common/handle_error.cc b/common/handle_error.cc deleted file mode 100644 index d5542369..00000000 --- a/common/handle_error.cc +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef NO_PYTHON - -#include -#include -#include "nextpnr.h" - -namespace py = pybind11; - -NEXTPNR_NAMESPACE_BEGIN - -// Parses the value of the active python exception -// NOTE SHOULD NOT BE CALLED IF NO EXCEPTION -std::string parse_python_exception() -{ - PyObject *type_ptr = NULL, *value_ptr = NULL, *traceback_ptr = NULL; - // Fetch the exception info from the Python C API - PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr); - - // Fallback error - std::string ret("Unfetchable Python error"); - // If the fetch got a type pointer, parse the type into the exception string - if (type_ptr != NULL) { - py::object obj = py::reinterpret_borrow(type_ptr); - // If a valid string extraction is available, use it - // otherwise use fallback - if (py::isinstance(obj)) - ret = obj.cast(); - else - ret = "Unknown exception type"; - } - // Do the same for the exception value (the stringification of the - // exception) - if (value_ptr != NULL) { - py::object obj = py::reinterpret_borrow(value_ptr); - if (py::isinstance(obj)) - ret += ": " + obj.cast(); - else - ret += std::string(": Unparseable Python error: "); - } - // Parse lines from the traceback using the Python traceback module - if (traceback_ptr != NULL) { - py::handle h_tb(traceback_ptr); - // Load the traceback module and the format_tb function - py::object tb(py::module::import("traceback")); - py::object fmt_tb(tb.attr("format_tb")); - // Call format_tb to get a list of traceback strings - py::object tb_list(fmt_tb(h_tb)); - // Join the traceback strings into a single string - py::object tb_str(py::str("\n") + tb_list); - // Extract the string, check the extraction, and fallback in necessary - if (py::isinstance(tb_str)) - ret += ": " + tb_str.cast(); - else - ret += std::string(": Unparseable Python traceback"); - } - return ret; -} - -NEXTPNR_NAMESPACE_END - -#endif // NO_PYTHON \ No newline at end of file diff --git a/common/hashlib.h b/common/hashlib.h deleted file mode 100644 index 2f7357e2..00000000 --- a/common/hashlib.h +++ /dev/null @@ -1,1210 +0,0 @@ -// This is free and unencumbered software released into the public domain. -// -// Anyone is free to copy, modify, publish, use, compile, sell, or -// distribute this software, either in source code form or as a compiled -// binary, for any purpose, commercial or non-commercial, and by any -// means. - -// ------------------------------------------------------- -// Written by Claire Xen in 2014 -// ------------------------------------------------------- - -#ifndef HASHLIB_H -#define HASHLIB_H - -#include -#include -#include -#include -#include - -#include "nextpnr_assertions.h" -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -const int hashtable_size_trigger = 2; -const int hashtable_size_factor = 3; - -// Cantor pairing function for two non-negative integers -// https://en.wikipedia.org/wiki/Pairing_function -inline unsigned int mkhash(unsigned int a, unsigned int b) { return (a * a + 3 * a + 2 * a * b + b + b * b) / 2; } - -// traditionally 5381 is used as starting value for the djb2 hash -const unsigned int mkhash_init = 5381; - -// The ADD version of DJB2 -// (use this version for cache locality in b) -inline unsigned int mkhash_add(unsigned int a, unsigned int b) { return ((a << 5) + a) + b; } - -inline unsigned int mkhash_xorshift(unsigned int a) -{ - if (sizeof(a) == 4) { - a ^= a << 13; - a ^= a >> 17; - a ^= a << 5; - } else if (sizeof(a) == 8) { - a ^= a << 13; - a ^= a >> 7; - a ^= a << 17; - } else - NPNR_ASSERT_FALSE("mkhash_xorshift() only implemented for 32 bit and 64 bit ints"); - return a; -} - -template struct hash_ops -{ - static inline bool cmp(const T &a, const T &b) { return a == b; } - static inline unsigned int hash(const T &a) { return a.hash(); } -}; - -struct hash_int_ops -{ - template static inline bool cmp(T a, T b) { return a == b; } -}; - -template <> struct hash_ops : hash_int_ops -{ - static inline unsigned int hash(bool a) { return a ? 1 : 0; } -}; -template <> struct hash_ops : hash_int_ops -{ - static inline unsigned int hash(int32_t a) { return a; } -}; -template <> struct hash_ops : hash_int_ops -{ - static inline unsigned int hash(int64_t a) { return mkhash((unsigned int)(a), (unsigned int)(a >> 32)); } -}; - -template <> struct hash_ops : hash_int_ops -{ - static inline unsigned int hash(uint32_t a) { return a; } -}; -template <> struct hash_ops : hash_int_ops -{ - static inline unsigned int hash(uint64_t a) { return mkhash((unsigned int)(a), (unsigned int)(a >> 32)); } -}; - -template <> struct hash_ops -{ - static inline bool cmp(const std::string &a, const std::string &b) { return a == b; } - static inline unsigned int hash(const std::string &a) - { - unsigned int v = 0; - for (auto c : a) - v = mkhash(v, c); - return v; - } -}; - -template struct hash_ops> -{ - static inline bool cmp(std::pair a, std::pair b) { return a == b; } - static inline unsigned int hash(std::pair a) - { - return mkhash(hash_ops

::hash(a.first), hash_ops::hash(a.second)); - } -}; - -template struct hash_ops> -{ - static inline bool cmp(std::tuple a, std::tuple b) { return a == b; } - template - static inline typename std::enable_if::type hash(std::tuple) - { - return mkhash_init; - } - template - static inline typename std::enable_if::type hash(std::tuple a) - { - typedef hash_ops>::type> element_ops_t; - return mkhash(hash(a), element_ops_t::hash(std::get(a))); - } -}; - -template struct hash_ops> -{ - static inline bool cmp(std::vector a, std::vector b) { return a == b; } - static inline unsigned int hash(std::vector a) - { - unsigned int h = mkhash_init; - for (auto k : a) - h = mkhash(h, hash_ops::hash(k)); - return h; - } -}; - -template struct hash_ops> -{ - static inline bool cmp(std::array a, std::array b) { return a == b; } - static inline unsigned int hash(std::array a) - { - unsigned int h = mkhash_init; - for (auto k : a) - h = mkhash(h, hash_ops::hash(k)); - return h; - } -}; - -struct hash_cstr_ops -{ - static inline bool cmp(const char *a, const char *b) - { - for (int i = 0; a[i] || b[i]; i++) - if (a[i] != b[i]) - return false; - return true; - } - static inline unsigned int hash(const char *a) - { - unsigned int hash = mkhash_init; - while (*a) - hash = mkhash(hash, *(a++)); - return hash; - } -}; - -struct hash_ptr_ops -{ - static inline bool cmp(const void *a, const void *b) { return a == b; } - static inline unsigned int hash(const void *a) { return (uintptr_t)a; } -}; - -struct hash_obj_ops -{ - static inline bool cmp(const void *a, const void *b) { return a == b; } - template static inline unsigned int hash(const T *a) { return a ? a->hash() : 0; } -}; - -template inline unsigned int mkhash(const T &v) { return hash_ops().hash(v); } - -inline int hashtable_size(int min_size) -{ - static std::vector zero_and_some_primes = { - 0, 23, 29, 37, 47, 59, 79, 101, 127, 163, - 211, 269, 337, 431, 541, 677, 853, 1069, 1361, 1709, - 2137, 2677, 3347, 4201, 5261, 6577, 8231, 10289, 12889, 16127, - 20161, 25219, 31531, 39419, 49277, 61603, 77017, 96281, 120371, 150473, - 188107, 235159, 293957, 367453, 459317, 574157, 717697, 897133, 1121423, 1401791, - 1752239, 2190299, 2737937, 3422429, 4278037, 5347553, 6684443, 8355563, 10444457, 13055587, - 16319519, 20399411, 25499291, 31874149, 39842687, 49803361, 62254207, 77817767, 97272239, 121590311, - 151987889, 189984863, 237481091, 296851369, 371064217}; - - for (auto p : zero_and_some_primes) - if (p >= min_size) - return p; - - if (sizeof(int) == 4) - throw std::length_error("hash table exceeded maximum size. use a ILP64 abi for larger tables."); - - for (auto p : zero_and_some_primes) - if (100129 * p > min_size) - return 100129 * p; - - throw std::length_error("hash table exceeded maximum size."); -} - -template > class dict; -template > class idict; -template > class pool; -template > class mfp; - -template class dict -{ - struct entry_t - { - std::pair udata; - int next; - - entry_t() {} - entry_t(const std::pair &udata, int next) : udata(udata), next(next) {} - entry_t(std::pair &&udata, int next) : udata(std::move(udata)), next(next) {} - bool operator<(const entry_t &other) const { return udata.first < other.udata.first; } - }; - - std::vector hashtable; - std::vector entries; - OPS ops; - -#ifdef NDEBUG - static inline void do_assert(bool) {} -#else - static inline void do_assert(bool cond) { NPNR_ASSERT(cond); } -#endif - - int do_hash(const K &key) const - { - unsigned int hash = 0; - if (!hashtable.empty()) - hash = ops.hash(key) % (unsigned int)(hashtable.size()); - return hash; - } - - void do_rehash() - { - hashtable.clear(); - hashtable.resize(hashtable_size(entries.capacity() * hashtable_size_factor), -1); - - for (int i = 0; i < int(entries.size()); i++) { - do_assert(-1 <= entries[i].next && entries[i].next < int(entries.size())); - int hash = do_hash(entries[i].udata.first); - entries[i].next = hashtable[hash]; - hashtable[hash] = i; - } - } - - int do_erase(int index, int hash) - { - do_assert(index < int(entries.size())); - if (hashtable.empty() || index < 0) - return 0; - - int k = hashtable[hash]; - do_assert(0 <= k && k < int(entries.size())); - - if (k == index) { - hashtable[hash] = entries[index].next; - } else { - while (entries[k].next != index) { - k = entries[k].next; - do_assert(0 <= k && k < int(entries.size())); - } - entries[k].next = entries[index].next; - } - - int back_idx = entries.size() - 1; - - if (index != back_idx) { - int back_hash = do_hash(entries[back_idx].udata.first); - - k = hashtable[back_hash]; - do_assert(0 <= k && k < int(entries.size())); - - if (k == back_idx) { - hashtable[back_hash] = index; - } else { - while (entries[k].next != back_idx) { - k = entries[k].next; - do_assert(0 <= k && k < int(entries.size())); - } - entries[k].next = index; - } - - entries[index] = std::move(entries[back_idx]); - } - - entries.pop_back(); - - if (entries.empty()) - hashtable.clear(); - - return 1; - } - - int do_lookup(const K &key, int &hash) const - { - if (hashtable.empty()) - return -1; - - if (entries.size() * hashtable_size_trigger > hashtable.size()) { - ((dict *)this)->do_rehash(); - hash = do_hash(key); - } - - int index = hashtable[hash]; - - while (index >= 0 && !ops.cmp(entries[index].udata.first, key)) { - index = entries[index].next; - do_assert(-1 <= index && index < int(entries.size())); - } - - return index; - } - - int do_insert(const K &key, int &hash) - { - if (hashtable.empty()) { - entries.emplace_back(std::pair(key, T()), -1); - do_rehash(); - hash = do_hash(key); - } else { - entries.emplace_back(std::pair(key, T()), hashtable[hash]); - hashtable[hash] = entries.size() - 1; - } - return entries.size() - 1; - } - - int do_insert(const std::pair &value, int &hash) - { - if (hashtable.empty()) { - entries.emplace_back(value, -1); - do_rehash(); - hash = do_hash(value.first); - } else { - entries.emplace_back(value, hashtable[hash]); - hashtable[hash] = entries.size() - 1; - } - return entries.size() - 1; - } - - int do_insert(std::pair &&rvalue, int &hash) - { - if (hashtable.empty()) { - auto key = rvalue.first; - entries.emplace_back(std::forward>(rvalue), -1); - do_rehash(); - hash = do_hash(key); - } else { - entries.emplace_back(std::forward>(rvalue), hashtable[hash]); - hashtable[hash] = entries.size() - 1; - } - return entries.size() - 1; - } - - public: - using key_type = K; - using mapped_type = T; - using value_type = std::pair; - - class const_iterator : public std::iterator> - { - friend class dict; - - protected: - const dict *ptr; - int index; - const_iterator(const dict *ptr, int index) : ptr(ptr), index(index) {} - - public: - const_iterator() {} - const_iterator operator++() - { - index--; - return *this; - } - const_iterator operator+=(int amt) - { - index -= amt; - return *this; - } - bool operator<(const const_iterator &other) const { return index > other.index; } - bool operator==(const const_iterator &other) const { return index == other.index; } - bool operator!=(const const_iterator &other) const { return index != other.index; } - const std::pair &operator*() const { return ptr->entries[index].udata; } - const std::pair *operator->() const { return &ptr->entries[index].udata; } - }; - - class iterator : public std::iterator> - { - friend class dict; - - protected: - dict *ptr; - int index; - iterator(dict *ptr, int index) : ptr(ptr), index(index) {} - - public: - iterator() {} - iterator operator++() - { - index--; - return *this; - } - iterator operator+=(int amt) - { - index -= amt; - return *this; - } - bool operator<(const iterator &other) const { return index > other.index; } - bool operator==(const iterator &other) const { return index == other.index; } - bool operator!=(const iterator &other) const { return index != other.index; } - std::pair &operator*() { return ptr->entries[index].udata; } - std::pair *operator->() { return &ptr->entries[index].udata; } - const std::pair &operator*() const { return ptr->entries[index].udata; } - const std::pair *operator->() const { return &ptr->entries[index].udata; } - operator const_iterator() const { return const_iterator(ptr, index); } - }; - - dict() {} - - dict(const dict &other) - { - entries = other.entries; - do_rehash(); - } - - dict(dict &&other) { swap(other); } - - dict &operator=(const dict &other) - { - entries = other.entries; - do_rehash(); - return *this; - } - - dict &operator=(dict &&other) - { - clear(); - swap(other); - return *this; - } - - dict(const std::initializer_list> &list) - { - for (auto &it : list) - insert(it); - } - - template dict(InputIterator first, InputIterator last) { insert(first, last); } - - template void insert(InputIterator first, InputIterator last) - { - for (; first != last; ++first) - insert(*first); - } - - std::pair insert(const K &key) - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - if (i >= 0) - return std::pair(iterator(this, i), false); - i = do_insert(key, hash); - return std::pair(iterator(this, i), true); - } - - std::pair insert(const std::pair &value) - { - int hash = do_hash(value.first); - int i = do_lookup(value.first, hash); - if (i >= 0) - return std::pair(iterator(this, i), false); - i = do_insert(value, hash); - return std::pair(iterator(this, i), true); - } - - std::pair insert(std::pair &&rvalue) - { - int hash = do_hash(rvalue.first); - int i = do_lookup(rvalue.first, hash); - if (i >= 0) - return std::pair(iterator(this, i), false); - i = do_insert(std::forward>(rvalue), hash); - return std::pair(iterator(this, i), true); - } - - std::pair emplace(K const &key, T const &value) - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - if (i >= 0) - return std::pair(iterator(this, i), false); - i = do_insert(std::make_pair(key, value), hash); - return std::pair(iterator(this, i), true); - } - - std::pair emplace(K const &key, T &&rvalue) - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - if (i >= 0) - return std::pair(iterator(this, i), false); - i = do_insert(std::make_pair(key, std::forward(rvalue)), hash); - return std::pair(iterator(this, i), true); - } - - std::pair emplace(K &&rkey, T const &value) - { - int hash = do_hash(rkey); - int i = do_lookup(rkey, hash); - if (i >= 0) - return std::pair(iterator(this, i), false); - i = do_insert(std::make_pair(std::forward(rkey), value), hash); - return std::pair(iterator(this, i), true); - } - - std::pair emplace(K &&rkey, T &&rvalue) - { - int hash = do_hash(rkey); - int i = do_lookup(rkey, hash); - if (i >= 0) - return std::pair(iterator(this, i), false); - i = do_insert(std::make_pair(std::forward(rkey), std::forward(rvalue)), hash); - return std::pair(iterator(this, i), true); - } - - int erase(const K &key) - { - int hash = do_hash(key); - int index = do_lookup(key, hash); - return do_erase(index, hash); - } - - iterator erase(iterator it) - { - int hash = do_hash(it->first); - do_erase(it.index, hash); - return ++it; - } - - int count(const K &key) const - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - return i < 0 ? 0 : 1; - } - - int count(const K &key, const_iterator it) const - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - return i < 0 || i > it.index ? 0 : 1; - } - - iterator find(const K &key) - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - if (i < 0) - return end(); - return iterator(this, i); - } - - const_iterator find(const K &key) const - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - if (i < 0) - return end(); - return const_iterator(this, i); - } - - T &at(const K &key) - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - if (i < 0) - throw std::out_of_range("dict::at()"); - return entries[i].udata.second; - } - - const T &at(const K &key) const - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - if (i < 0) - throw std::out_of_range("dict::at()"); - return entries[i].udata.second; - } - - const T &at(const K &key, const T &defval) const - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - if (i < 0) - return defval; - return entries[i].udata.second; - } - - T &operator[](const K &key) - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - if (i < 0) - i = do_insert(std::pair(key, T()), hash); - return entries[i].udata.second; - } - - template > void sort(Compare comp = Compare()) - { - std::sort(entries.begin(), entries.end(), - [comp](const entry_t &a, const entry_t &b) { return comp(b.udata.first, a.udata.first); }); - do_rehash(); - } - - void swap(dict &other) - { - hashtable.swap(other.hashtable); - entries.swap(other.entries); - } - - bool operator==(const dict &other) const - { - if (size() != other.size()) - return false; - for (auto &it : entries) { - auto oit = other.find(it.udata.first); - if (oit == other.end() || !(oit->second == it.udata.second)) - return false; - } - return true; - } - - bool operator!=(const dict &other) const { return !operator==(other); } - - unsigned int hash() const - { - unsigned int h = mkhash_init; - for (auto &entry : entries) { - h ^= hash_ops::hash(entry.udata.first); - h ^= hash_ops::hash(entry.udata.second); - } - return h; - } - - void reserve(size_t n) { entries.reserve(n); } - size_t size() const { return entries.size(); } - bool empty() const { return entries.empty(); } - void clear() - { - hashtable.clear(); - entries.clear(); - } - - iterator begin() { return iterator(this, int(entries.size()) - 1); } - iterator element(int n) { return iterator(this, int(entries.size()) - 1 - n); } - iterator end() { return iterator(nullptr, -1); } - - const_iterator begin() const { return const_iterator(this, int(entries.size()) - 1); } - const_iterator element(int n) const { return const_iterator(this, int(entries.size()) - 1 - n); } - const_iterator end() const { return const_iterator(nullptr, -1); } -}; - -template class pool -{ - template friend class idict; - - protected: - struct entry_t - { - K udata; - int next; - - entry_t() {} - entry_t(const K &udata, int next) : udata(udata), next(next) {} - entry_t(K &&udata, int next) : udata(std::move(udata)), next(next) {} - }; - - std::vector hashtable; - std::vector entries; - OPS ops; - -#ifdef NDEBUG - static inline void do_assert(bool) {} -#else - static inline void do_assert(bool cond) { NPNR_ASSERT(cond); } -#endif - - int do_hash(const K &key) const - { - unsigned int hash = 0; - if (!hashtable.empty()) - hash = ops.hash(key) % (unsigned int)(hashtable.size()); - return hash; - } - - void do_rehash() - { - hashtable.clear(); - hashtable.resize(hashtable_size(entries.capacity() * hashtable_size_factor), -1); - - for (int i = 0; i < int(entries.size()); i++) { - do_assert(-1 <= entries[i].next && entries[i].next < int(entries.size())); - int hash = do_hash(entries[i].udata); - entries[i].next = hashtable[hash]; - hashtable[hash] = i; - } - } - - int do_erase(int index, int hash) - { - do_assert(index < int(entries.size())); - if (hashtable.empty() || index < 0) - return 0; - - int k = hashtable[hash]; - if (k == index) { - hashtable[hash] = entries[index].next; - } else { - while (entries[k].next != index) { - k = entries[k].next; - do_assert(0 <= k && k < int(entries.size())); - } - entries[k].next = entries[index].next; - } - - int back_idx = entries.size() - 1; - - if (index != back_idx) { - int back_hash = do_hash(entries[back_idx].udata); - - k = hashtable[back_hash]; - if (k == back_idx) { - hashtable[back_hash] = index; - } else { - while (entries[k].next != back_idx) { - k = entries[k].next; - do_assert(0 <= k && k < int(entries.size())); - } - entries[k].next = index; - } - - entries[index] = std::move(entries[back_idx]); - } - - entries.pop_back(); - - if (entries.empty()) - hashtable.clear(); - - return 1; - } - - int do_lookup(const K &key, int &hash) const - { - if (hashtable.empty()) - return -1; - - if (entries.size() * hashtable_size_trigger > hashtable.size()) { - ((pool *)this)->do_rehash(); - hash = do_hash(key); - } - - int index = hashtable[hash]; - - while (index >= 0 && !ops.cmp(entries[index].udata, key)) { - index = entries[index].next; - do_assert(-1 <= index && index < int(entries.size())); - } - - return index; - } - - int do_insert(const K &value, int &hash) - { - if (hashtable.empty()) { - entries.emplace_back(value, -1); - do_rehash(); - hash = do_hash(value); - } else { - entries.emplace_back(value, hashtable[hash]); - hashtable[hash] = entries.size() - 1; - } - return entries.size() - 1; - } - - int do_insert(K &&rvalue, int &hash) - { - if (hashtable.empty()) { - entries.emplace_back(std::forward(rvalue), -1); - do_rehash(); - hash = do_hash(rvalue); - } else { - entries.emplace_back(std::forward(rvalue), hashtable[hash]); - hashtable[hash] = entries.size() - 1; - } - return entries.size() - 1; - } - - public: - class const_iterator : public std::iterator - { - friend class pool; - - protected: - const pool *ptr; - int index; - const_iterator(const pool *ptr, int index) : ptr(ptr), index(index) {} - - public: - const_iterator() {} - const_iterator operator++() - { - index--; - return *this; - } - bool operator==(const const_iterator &other) const { return index == other.index; } - bool operator!=(const const_iterator &other) const { return index != other.index; } - const K &operator*() const { return ptr->entries[index].udata; } - const K *operator->() const { return &ptr->entries[index].udata; } - }; - - class iterator : public std::iterator - { - friend class pool; - - protected: - pool *ptr; - int index; - iterator(pool *ptr, int index) : ptr(ptr), index(index) {} - - public: - iterator() {} - iterator operator++() - { - index--; - return *this; - } - bool operator==(const iterator &other) const { return index == other.index; } - bool operator!=(const iterator &other) const { return index != other.index; } - K &operator*() { return ptr->entries[index].udata; } - K *operator->() { return &ptr->entries[index].udata; } - const K &operator*() const { return ptr->entries[index].udata; } - const K *operator->() const { return &ptr->entries[index].udata; } - operator const_iterator() const { return const_iterator(ptr, index); } - }; - - pool() {} - - pool(const pool &other) - { - entries = other.entries; - do_rehash(); - } - - pool(pool &&other) { swap(other); } - - pool &operator=(const pool &other) - { - entries = other.entries; - do_rehash(); - return *this; - } - - pool &operator=(pool &&other) - { - clear(); - swap(other); - return *this; - } - - pool(const std::initializer_list &list) - { - for (auto &it : list) - insert(it); - } - - template pool(InputIterator first, InputIterator last) { insert(first, last); } - - template void insert(InputIterator first, InputIterator last) - { - for (; first != last; ++first) - insert(*first); - } - - std::pair insert(const K &value) - { - int hash = do_hash(value); - int i = do_lookup(value, hash); - if (i >= 0) - return std::pair(iterator(this, i), false); - i = do_insert(value, hash); - return std::pair(iterator(this, i), true); - } - - std::pair insert(K &&rvalue) - { - int hash = do_hash(rvalue); - int i = do_lookup(rvalue, hash); - if (i >= 0) - return std::pair(iterator(this, i), false); - i = do_insert(std::forward(rvalue), hash); - return std::pair(iterator(this, i), true); - } - - template std::pair emplace(Args &&...args) - { - return insert(K(std::forward(args)...)); - } - - int erase(const K &key) - { - int hash = do_hash(key); - int index = do_lookup(key, hash); - return do_erase(index, hash); - } - - iterator erase(iterator it) - { - int hash = do_hash(*it); - do_erase(it.index, hash); - return ++it; - } - - int count(const K &key) const - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - return i < 0 ? 0 : 1; - } - - int count(const K &key, const_iterator it) const - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - return i < 0 || i > it.index ? 0 : 1; - } - - iterator find(const K &key) - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - if (i < 0) - return end(); - return iterator(this, i); - } - - const_iterator find(const K &key) const - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - if (i < 0) - return end(); - return const_iterator(this, i); - } - - bool operator[](const K &key) - { - int hash = do_hash(key); - int i = do_lookup(key, hash); - return i >= 0; - } - - template > void sort(Compare comp = Compare()) - { - std::sort(entries.begin(), entries.end(), - [comp](const entry_t &a, const entry_t &b) { return comp(b.udata, a.udata); }); - do_rehash(); - } - - K pop() - { - iterator it = begin(); - K ret = *it; - erase(it); - return ret; - } - - void swap(pool &other) - { - hashtable.swap(other.hashtable); - entries.swap(other.entries); - } - - bool operator==(const pool &other) const - { - if (size() != other.size()) - return false; - for (auto &it : entries) - if (!other.count(it.udata)) - return false; - return true; - } - - bool operator!=(const pool &other) const { return !operator==(other); } - - bool hash() const - { - unsigned int hashval = mkhash_init; - for (auto &it : entries) - hashval ^= ops.hash(it.udata); - return hashval; - } - - void reserve(size_t n) { entries.reserve(n); } - size_t size() const { return entries.size(); } - bool empty() const { return entries.empty(); } - void clear() - { - hashtable.clear(); - entries.clear(); - } - - iterator begin() { return iterator(this, int(entries.size()) - 1); } - iterator element(int n) { return iterator(this, int(entries.size()) - 1 - n); } - iterator end() { return iterator(nullptr, -1); } - - const_iterator begin() const { return const_iterator(this, int(entries.size()) - 1); } - const_iterator element(int n) const { return const_iterator(this, int(entries.size()) - 1 - n); } - const_iterator end() const { return const_iterator(nullptr, -1); } -}; - -template class idict -{ - pool database; - - public: - class const_iterator : public std::iterator - { - friend class idict; - - protected: - const idict &container; - int index; - const_iterator(const idict &container, int index) : container(container), index(index) {} - - public: - const_iterator() {} - const_iterator operator++() - { - index++; - return *this; - } - bool operator==(const const_iterator &other) const { return index == other.index; } - bool operator!=(const const_iterator &other) const { return index != other.index; } - const K &operator*() const { return container[index]; } - const K *operator->() const { return &container[index]; } - }; - - int operator()(const K &key) - { - int hash = database.do_hash(key); - int i = database.do_lookup(key, hash); - if (i < 0) - i = database.do_insert(key, hash); - return i + offset; - } - - int at(const K &key) const - { - int hash = database.do_hash(key); - int i = database.do_lookup(key, hash); - if (i < 0) - throw std::out_of_range("idict::at()"); - return i + offset; - } - - int at(const K &key, int defval) const - { - int hash = database.do_hash(key); - int i = database.do_lookup(key, hash); - if (i < 0) - return defval; - return i + offset; - } - - int count(const K &key) const - { - int hash = database.do_hash(key); - int i = database.do_lookup(key, hash); - return i < 0 ? 0 : 1; - } - - void expect(const K &key, int i) - { - int j = (*this)(key); - if (i != j) - throw std::out_of_range("idict::expect()"); - } - - const K &operator[](int index) const { return database.entries.at(index - offset).udata; } - - void swap(idict &other) { database.swap(other.database); } - - void reserve(size_t n) { database.reserve(n); } - size_t size() const { return database.size(); } - bool empty() const { return database.empty(); } - void clear() { database.clear(); } - - const_iterator begin() const { return const_iterator(*this, offset); } - const_iterator element(int n) const { return const_iterator(*this, n); } - const_iterator end() const { return const_iterator(*this, offset + size()); } -}; - -template class mfp -{ - mutable idict database; - mutable std::vector parents; - - public: - typedef typename idict::const_iterator const_iterator; - - int operator()(const K &key) const - { - int i = database(key); - parents.resize(database.size(), -1); - return i; - } - - const K &operator[](int index) const { return database[index]; } - - int ifind(int i) const - { - int p = i, k = i; - - while (parents[p] != -1) - p = parents[p]; - - while (k != p) { - int next_k = parents[k]; - parents[k] = p; - k = next_k; - } - - return p; - } - - void imerge(int i, int j) - { - i = ifind(i); - j = ifind(j); - - if (i != j) - parents[i] = j; - } - - void ipromote(int i) - { - int k = i; - - while (k != -1) { - int next_k = parents[k]; - parents[k] = i; - k = next_k; - } - - parents[i] = -1; - } - - int lookup(const K &a) const { return ifind((*this)(a)); } - - const K &find(const K &a) const - { - int i = database.at(a, -1); - if (i < 0) - return a; - return (*this)[ifind(i)]; - } - - void merge(const K &a, const K &b) { imerge((*this)(a), (*this)(b)); } - - void promote(const K &a) - { - int i = database.at(a, -1); - if (i >= 0) - ipromote(i); - } - - void swap(mfp &other) - { - database.swap(other.database); - parents.swap(other.parents); - } - - void reserve(size_t n) { database.reserve(n); } - size_t size() const { return database.size(); } - bool empty() const { return database.empty(); } - void clear() - { - database.clear(); - parents.clear(); - } - - const_iterator begin() const { return database.begin(); } - const_iterator element(int n) const { return database.element(n); } - const_iterator end() const { return database.end(); } -}; - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/idstring.cc b/common/idstring.cc deleted file mode 100644 index 9e27ac6f..00000000 --- a/common/idstring.cc +++ /dev/null @@ -1,51 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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 "idstring.h" - -#include "basectx.h" - -NEXTPNR_NAMESPACE_BEGIN - -void IdString::set(const BaseCtx *ctx, const std::string &s) -{ - auto it = ctx->idstring_str_to_idx->find(s); - if (it == ctx->idstring_str_to_idx->end()) { - index = ctx->idstring_idx_to_str->size(); - auto insert_rc = ctx->idstring_str_to_idx->insert({s, index}); - ctx->idstring_idx_to_str->push_back(&insert_rc.first->first); - } else { - index = it->second; - } -} - -const std::string &IdString::str(const BaseCtx *ctx) const { return *ctx->idstring_idx_to_str->at(index); } - -const char *IdString::c_str(const BaseCtx *ctx) const { return str(ctx).c_str(); } - -void IdString::initialize_add(const BaseCtx *ctx, const char *s, int idx) -{ - NPNR_ASSERT(ctx->idstring_str_to_idx->count(s) == 0); - NPNR_ASSERT(int(ctx->idstring_idx_to_str->size()) == idx); - auto insert_rc = ctx->idstring_str_to_idx->insert({s, idx}); - ctx->idstring_idx_to_str->push_back(&insert_rc.first->first); -} - -NEXTPNR_NAMESPACE_END diff --git a/common/idstring.h b/common/idstring.h deleted file mode 100644 index 019e0a2a..00000000 --- a/common/idstring.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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 IDSTRING_H -#define IDSTRING_H - -#include -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct BaseCtx; - -struct IdString -{ - int index; - - static void initialize_arch(const BaseCtx *ctx); - - static void initialize_add(const BaseCtx *ctx, const char *s, int idx); - - constexpr IdString() : index(0) {} - explicit constexpr IdString(int index) : index(index) {} - - void set(const BaseCtx *ctx, const std::string &s); - - IdString(const BaseCtx *ctx, const std::string &s) { set(ctx, s); } - - IdString(const BaseCtx *ctx, const char *s) { set(ctx, s); } - - const std::string &str(const BaseCtx *ctx) const; - - const char *c_str(const BaseCtx *ctx) const; - - bool operator<(const IdString &other) const { return index < other.index; } - - bool operator==(const IdString &other) const { return index == other.index; } - - bool operator!=(const IdString &other) const { return index != other.index; } - - bool empty() const { return index == 0; } - - unsigned int hash() const { return index; } - - template bool in(Args... args) const - { - // Credit: https://articles.emptycrate.com/2016/05/14/folds_in_cpp11_ish.html - bool result = false; - (void)std::initializer_list{(result = result || in(args), 0)...}; - return result; - } - - bool in(const IdString &rhs) const { return *this == rhs; } -}; - -NEXTPNR_NAMESPACE_END - -#endif /* IDSTRING_H */ diff --git a/common/idstringlist.cc b/common/idstringlist.cc deleted file mode 100644 index 624622cf..00000000 --- a/common/idstringlist.cc +++ /dev/null @@ -1,80 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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 "idstringlist.h" -#include "context.h" - -NEXTPNR_NAMESPACE_BEGIN - -IdStringList IdStringList::parse(Context *ctx, const std::string &str) -{ - char delim = ctx->getNameDelimiter(); - size_t id_count = std::count(str.begin(), str.end(), delim) + 1; - IdStringList list(id_count); - size_t start = 0; - for (size_t i = 0; i < id_count; i++) { - size_t end = str.find(delim, start); - NPNR_ASSERT((i == (id_count - 1)) || (end != std::string::npos)); - list.ids[i] = ctx->id(str.substr(start, end - start)); - start = end + 1; - } - return list; -} - -void IdStringList::build_str(const Context *ctx, std::string &str) const -{ - char delim = ctx->getNameDelimiter(); - bool first = true; - str.clear(); - for (auto entry : ids) { - if (!first) - str += delim; - str += entry.str(ctx); - first = false; - } -} - -std::string IdStringList::str(const Context *ctx) const -{ - std::string s; - build_str(ctx, s); - 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 deleted file mode 100644 index 5e462d0e..00000000 --- a/common/idstringlist.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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 IDSTRING_LIST_H -#define IDSTRING_LIST_H - -#include -#include "hashlib.h" -#include "idstring.h" -#include "nextpnr_namespaces.h" -#include "sso_array.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct Context; - -struct IdStringList -{ - SSOArray ids; - - IdStringList() : ids(1, IdString()){}; - explicit IdStringList(size_t n) : ids(n, IdString()){}; - explicit IdStringList(IdString id) : ids(1, id){}; - template explicit IdStringList(const Tlist &list) : ids(list){}; - - static IdStringList parse(Context *ctx, const std::string &str); - void build_str(const Context *ctx, std::string &str) const; - std::string str(const Context *ctx) const; - - size_t size() const { return ids.size(); } - const IdString *begin() const { return ids.begin(); } - const IdString *end() const { return ids.end(); } - const IdString &operator[](size_t idx) const { return ids[idx]; } - bool operator==(const IdStringList &other) const { return ids == other.ids; } - bool operator!=(const IdStringList &other) const { return ids != other.ids; } - bool operator<(const IdStringList &other) const - { - if (size() > other.size()) - return false; - if (size() < other.size()) - return true; - for (size_t i = 0; i < size(); i++) { - IdString a = ids[i], b = other[i]; - if (a.index < b.index) - return true; - if (a.index > b.index) - return false; - } - return false; - } - - static IdStringList concat(IdStringList a, IdStringList b); - static IdStringList concat(IdString a, IdString b) { return concat(IdStringList(a), IdStringList(b)); } - static IdStringList concat(IdStringList a, IdString b) { return concat(a, IdStringList(b)); } - static IdStringList concat(IdString a, IdStringList b) { return concat(IdStringList(a), b); } - - IdStringList slice(size_t s, size_t e) const; - - unsigned int hash() const - { - unsigned int h = mkhash_init; - for (const auto &val : ids) - h = mkhash(h, val.hash()); - return h; - } -}; - -NEXTPNR_NAMESPACE_END - -#endif /* IDSTRING_LIST_H */ diff --git a/common/indexed_store.h b/common/indexed_store.h deleted file mode 100644 index df607c13..00000000 --- a/common/indexed_store.h +++ /dev/null @@ -1,297 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021-22 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 INDEXED_STORE_H -#define INDEXED_STORE_H - -#include -#include -#include -#include - -#include "nextpnr_assertions.h" -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -template struct store_index -{ - int32_t m_index = -1; - store_index() = default; - explicit store_index(int32_t index) : m_index(index){}; - int32_t idx() const { return m_index; } - void set(int32_t index) { m_index = index; } - bool empty() const { return m_index == -1; } - bool operator==(const store_index &other) const { return m_index == other.m_index; } - bool operator!=(const store_index &other) const { return m_index != other.m_index; } - bool operator<(const store_index &other) const { return m_index < other.m_index; } - unsigned int hash() const { return m_index; } - - operator bool() const { return !empty(); } - operator int() const = delete; - bool operator!() const { return empty(); } -}; - -// "Slotted" indexed object store -template class indexed_store -{ - private: - // This should move to using std::optional at some point - class slot - { - private: - alignas(T) unsigned char storage[sizeof(T)]; - int32_t next_free; - bool active; - inline T &obj() { return reinterpret_cast(storage); } - inline const T &obj() const { return reinterpret_cast(storage); } - friend class indexed_store; - - public: - slot() : next_free(std::numeric_limits::max()), active(false){}; - slot(slot &&other) : next_free(other.next_free), active(other.active) - { - if (active) - ::new (static_cast(&storage)) T(std::move(other.obj())); - }; - - slot(const slot &other) : next_free(other.next_free), active(other.active) - { - if (active) - ::new (static_cast(&storage)) T(other.obj()); - }; - - template void create(Args &&...args) - { - NPNR_ASSERT(!active); - active = true; - ::new (static_cast(&storage)) T(std::forward(args)...); - } - bool empty() const { return !active; } - T &get() - { - NPNR_ASSERT(active); - return reinterpret_cast(storage); - } - const T &get() const - { - NPNR_ASSERT(active); - return reinterpret_cast(storage); - } - void free(int32_t first_free) - { - NPNR_ASSERT(active); - obj().~T(); - active = false; - next_free = first_free; - } - ~slot() - { - if (active) - obj().~T(); - } - }; - - std::vector slots; - int32_t first_free = 0; - int32_t active_count = 0; - - public: - // Create a new entry and return its index - template store_index add(Args &&...args) - { - ++active_count; - if (first_free == int32_t(slots.size())) { - slots.emplace_back(); - slots.back().create(std::forward(args)...); - ++first_free; - return store_index(int32_t(slots.size()) - 1); - } else { - int32_t idx = first_free; - auto &slot = slots.at(idx); - first_free = slot.next_free; - slot.create(std::forward(args)...); - return store_index(idx); - } - } - - // Remove an entry at an index - void remove(store_index idx) - { - --active_count; - slots.at(idx.m_index).free(first_free); - first_free = idx.m_index; - } - - void clear() - { - active_count = 0; - first_free = 0; - slots.clear(); - } - - // Number of live entries - int32_t entries() const { return active_count; } - bool empty() const { return (entries() == 0); } - - // Reserve a certain amount of space - void reserve(int32_t size) { slots.reserve(size); } - - // Check if an index exists - int32_t count(store_index idx) - { - if (idx.m_index < 0 || idx.m_index >= int32_t(slots.size())) - return 0; - return slots.at(idx.m_index).empty() ? 0 : 1; - } - - // Get an item by index - T &at(store_index idx) { return slots.at(idx.m_index).get(); } - const T &at(store_index idx) const { return slots.at(idx.m_index).get(); } - T &operator[](store_index idx) { return slots.at(idx.m_index).get(); } - const T &operator[](store_index idx) const { return slots.at(idx.m_index).get(); } - - // Total size of the container - int32_t capacity() const { return int32_t(slots.size()); } - - // Iterate over items - template class enumerated_iterator; - - class iterator - { - private: - indexed_store *base; - int32_t index = 0; - - public: - iterator(indexed_store *base, int32_t index) : base(base), index(index){}; - inline bool operator!=(const iterator &other) const { return other.index != index; } - inline bool operator==(const iterator &other) const { return other.index == index; } - inline iterator operator++() - { - // skip over unused slots - do { - index++; - } while (index < int32_t(base->slots.size()) && !base->slots.at(index).active); - return *this; - } - inline iterator operator++(int) - { - iterator prior(*this); - do { - index++; - } while (index < int32_t(base->slots.size()) && !base->slots.at(index).active); - return prior; - } - T &operator*() { return base->at(store_index(index)); } - template friend class indexed_store::enumerated_iterator; - }; - iterator begin() - { - auto it = iterator{this, -1}; - ++it; - return it; - } - iterator end() { return iterator{this, int32_t(slots.size())}; } - - class const_iterator - { - private: - const indexed_store *base; - int32_t index = 0; - - public: - const_iterator(const indexed_store *base, int32_t index) : base(base), index(index){}; - inline bool operator!=(const const_iterator &other) const { return other.index != index; } - inline bool operator==(const const_iterator &other) const { return other.index == index; } - inline const_iterator operator++() - { - // skip over unused slots - do { - index++; - } while (index < int32_t(base->slots.size()) && !base->slots.at(index).active); - return *this; - } - inline const_iterator operator++(int) - { - iterator prior(*this); - do { - index++; - } while (index < int32_t(base->slots.size()) && !base->slots.at(index).active); - return prior; - } - const T &operator*() { return base->at(store_index(index)); } - template friend class indexed_store::enumerated_iterator; - }; - const_iterator begin() const - { - auto it = const_iterator{this, -1}; - ++it; - return it; - } - const_iterator end() const { return const_iterator{this, int32_t(slots.size())}; } - - template struct enumerated_item - { - enumerated_item(int32_t index, T &value) : index(index), value(value){}; - store_index> index; - S &value; - }; - - template class enumerated_iterator - { - private: - It base; - - public: - enumerated_iterator(const It &base) : base(base){}; - inline bool operator!=(const enumerated_iterator &other) const { return other.base != base; } - inline bool operator==(const enumerated_iterator &other) const { return other.base == base; } - inline enumerated_iterator operator++() - { - ++base; - return *this; - } - inline enumerated_iterator operator++(int) - { - iterator prior(*this); - ++base; - return prior; - } - enumerated_item operator*() { return enumerated_item{base.index, *base}; } - }; - - template struct enumerated_range - { - enumerated_range(const It &begin, const It &end) : m_begin(begin), m_end(end){}; - enumerated_iterator m_begin, m_end; - enumerated_iterator begin() { return m_begin; } - enumerated_iterator end() { return m_end; } - }; - - enumerated_range enumerate() { return enumerated_range{begin(), end()}; } - enumerated_range enumerate() const - { - return enumerated_range{begin(), end()}; - } -}; - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/kernel/arch_api.h b/common/kernel/arch_api.h new file mode 100644 index 00000000..14a30652 --- /dev/null +++ b/common/kernel/arch_api.h @@ -0,0 +1,158 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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_API_H +#define ARCH_API_H + +#include + +#include "basectx.h" +#include "idstring.h" +#include "idstringlist.h" +#include "nextpnr_assertions.h" +#include "nextpnr_namespaces.h" +#include "nextpnr_types.h" + +NEXTPNR_NAMESPACE_BEGIN + +// The specification of the Arch API (pure virtual) +template struct ArchAPI : BaseCtx +{ + // Basic config + virtual IdString archId() const = 0; + virtual std::string getChipName() const = 0; + virtual typename R::ArchArgsT archArgs() const = 0; + virtual IdString archArgsToId(typename R::ArchArgsT args) const = 0; + virtual int getGridDimX() const = 0; + virtual int getGridDimY() const = 0; + virtual int getTileBelDimZ(int x, int y) const = 0; + virtual int getTilePipDimZ(int x, int y) const = 0; + virtual char getNameDelimiter() const = 0; + // Bel methods + virtual typename R::AllBelsRangeT getBels() const = 0; + virtual IdStringList getBelName(BelId bel) const = 0; + virtual BelId getBelByName(IdStringList name) const = 0; + virtual uint32_t getBelChecksum(BelId bel) const = 0; + virtual void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) = 0; + virtual void unbindBel(BelId bel) = 0; + virtual Loc getBelLocation(BelId bel) const = 0; + virtual BelId getBelByLocation(Loc loc) const = 0; + virtual typename R::TileBelsRangeT getBelsByTile(int x, int y) const = 0; + virtual bool getBelGlobalBuf(BelId bel) const = 0; + virtual bool checkBelAvail(BelId bel) const = 0; + virtual CellInfo *getBoundBelCell(BelId bel) const = 0; + virtual CellInfo *getConflictingBelCell(BelId bel) const = 0; + virtual IdString getBelType(BelId bel) const = 0; + virtual bool getBelHidden(BelId bel) const = 0; + virtual typename R::BelAttrsRangeT getBelAttrs(BelId bel) const = 0; + virtual WireId getBelPinWire(BelId bel, IdString pin) const = 0; + virtual PortType getBelPinType(BelId bel, IdString pin) const = 0; + virtual typename R::BelPinsRangeT getBelPins(BelId bel) const = 0; + virtual typename R::CellBelPinRangeT getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const = 0; + // Wire methods + virtual typename R::AllWiresRangeT getWires() const = 0; + virtual WireId getWireByName(IdStringList name) const = 0; + virtual IdStringList getWireName(WireId wire) const = 0; + virtual IdString getWireType(WireId wire) const = 0; + virtual typename R::WireAttrsRangeT getWireAttrs(WireId) const = 0; + virtual typename R::DownhillPipRangeT getPipsDownhill(WireId wire) const = 0; + virtual typename R::UphillPipRangeT getPipsUphill(WireId wire) const = 0; + virtual typename R::WireBelPinRangeT getWireBelPins(WireId wire) const = 0; + virtual uint32_t getWireChecksum(WireId wire) const = 0; + virtual void bindWire(WireId wire, NetInfo *net, PlaceStrength strength) = 0; + virtual void unbindWire(WireId wire) = 0; + virtual bool checkWireAvail(WireId wire) const = 0; + virtual NetInfo *getBoundWireNet(WireId wire) const = 0; + virtual WireId getConflictingWireWire(WireId wire) const = 0; + virtual NetInfo *getConflictingWireNet(WireId wire) const = 0; + virtual DelayQuad getWireDelay(WireId wire) const = 0; + // Pip methods + virtual typename R::AllPipsRangeT getPips() const = 0; + virtual PipId getPipByName(IdStringList name) const = 0; + virtual IdStringList getPipName(PipId pip) const = 0; + virtual IdString getPipType(PipId pip) const = 0; + virtual typename R::PipAttrsRangeT getPipAttrs(PipId) const = 0; + virtual uint32_t getPipChecksum(PipId pip) const = 0; + virtual void bindPip(PipId pip, NetInfo *net, PlaceStrength strength) = 0; + virtual void unbindPip(PipId pip) = 0; + virtual bool checkPipAvail(PipId pip) const = 0; + virtual bool checkPipAvailForNet(PipId pip, NetInfo *net) const = 0; + virtual NetInfo *getBoundPipNet(PipId pip) const = 0; + virtual WireId getConflictingPipWire(PipId pip) const = 0; + virtual NetInfo *getConflictingPipNet(PipId pip) const = 0; + virtual WireId getPipSrcWire(PipId pip) const = 0; + virtual WireId getPipDstWire(PipId pip) const = 0; + virtual DelayQuad getPipDelay(PipId pip) const = 0; + virtual Loc getPipLocation(PipId pip) const = 0; + // Group methods + virtual GroupId getGroupByName(IdStringList name) const = 0; + virtual IdStringList getGroupName(GroupId group) const = 0; + virtual typename R::AllGroupsRangeT getGroups() const = 0; + virtual typename R::GroupBelsRangeT getGroupBels(GroupId group) const = 0; + virtual typename R::GroupWiresRangeT getGroupWires(GroupId group) const = 0; + virtual typename R::GroupPipsRangeT getGroupPips(GroupId group) const = 0; + virtual typename R::GroupGroupsRangeT getGroupGroups(GroupId group) const = 0; + // Delay Methods + virtual delay_t predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const = 0; + virtual delay_t getDelayEpsilon() const = 0; + virtual delay_t getRipupDelayPenalty() const = 0; + virtual float getDelayNS(delay_t v) const = 0; + virtual delay_t getDelayFromNS(float ns) const = 0; + virtual uint32_t getDelayChecksum(delay_t v) const = 0; + virtual bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const = 0; + virtual delay_t estimateDelay(WireId src, WireId dst) const = 0; + virtual ArcBounds getRouteBoundingBox(WireId src, WireId dst) const = 0; + // Decal methods + virtual typename R::DecalGfxRangeT getDecalGraphics(DecalId decal) const = 0; + virtual DecalXY getBelDecal(BelId bel) const = 0; + virtual DecalXY getWireDecal(WireId wire) const = 0; + virtual DecalXY getPipDecal(PipId pip) const = 0; + virtual DecalXY getGroupDecal(GroupId group) const = 0; + // Cell timing methods + virtual bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const = 0; + virtual TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const = 0; + virtual TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const = 0; + // Placement validity checks + virtual bool isValidBelForCellType(IdString cell_type, BelId bel) const = 0; + virtual IdString getBelBucketName(BelBucketId bucket) const = 0; + virtual BelBucketId getBelBucketByName(IdString name) const = 0; + virtual BelBucketId getBelBucketForBel(BelId bel) const = 0; + virtual BelBucketId getBelBucketForCellType(IdString cell_type) const = 0; + virtual bool isBelLocationValid(BelId bel) const = 0; + virtual typename R::CellTypeRangeT getCellTypes() const = 0; + virtual typename R::BelBucketRangeT getBelBuckets() const = 0; + virtual typename R::BucketBelRangeT getBelsInBucket(BelBucketId bucket) const = 0; + // Cluster methods + virtual CellInfo *getClusterRootCell(ClusterId cluster) const = 0; + virtual ArcBounds getClusterBounds(ClusterId cluster) const = 0; + virtual Loc getClusterOffset(const CellInfo *cell) const = 0; + virtual bool isClusterStrict(const CellInfo *cell) const = 0; + virtual bool getClusterPlacement(ClusterId cluster, BelId root_bel, + std::vector> &placement) const = 0; + // Flow methods + virtual bool pack() = 0; + virtual bool place() = 0; + virtual bool route() = 0; + virtual void assignArchInfo() = 0; +}; + +NEXTPNR_NAMESPACE_END + +#endif /* ARCH_API_H */ diff --git a/common/kernel/arch_pybindings_shared.h b/common/kernel/arch_pybindings_shared.h new file mode 100644 index 00000000..b3dc0506 --- /dev/null +++ b/common/kernel/arch_pybindings_shared.h @@ -0,0 +1,147 @@ +// Common Python bindings #included by all arches + +readonly_wrapper>::def_wrap(ctx_cls, + "cells"); +readonly_wrapper>::def_wrap(ctx_cls, "nets"); +readonly_wrapper>::def_wrap( + ctx_cls, "net_aliases"); +readonly_wrapper>::def_wrap( + ctx_cls, "hierarchy"); +readwrite_wrapper, + conv_from_str>::def_wrap(ctx_cls, "top_module"); +readonly_wrapper>::def_wrap(ctx_cls, "timing_result"); + +fn_wrapper_0a>::def_wrap( + ctx_cls, "getNameDelimiter"); + +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getNetByAlias"); +fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "addClock"); +fn_wrapper_5a_v, pass_through, pass_through, pass_through, + pass_through>::def_wrap(ctx_cls, "createRectangularRegion"); +fn_wrapper_2a_v, + conv_from_str>::def_wrap(ctx_cls, "addBelToRegion"); +fn_wrapper_2a_v, conv_from_str>::def_wrap(ctx_cls, "constrainCellToRegion"); + +fn_wrapper_2a, + addr_and_unwrap, unwrap_context>::def_wrap(ctx_cls, "getNetinfoRouteDelay"); + +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "createNet"); +fn_wrapper_3a_v, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "connectPort"); +fn_wrapper_2a_v, + conv_from_str>::def_wrap(ctx_cls, "disconnectPort"); +fn_wrapper_1a_v>::def_wrap( + ctx_cls, "ripupNet"); +fn_wrapper_1a_v>::def_wrap(ctx_cls, "lockNetRouting"); + +fn_wrapper_2a, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "createCell"); +fn_wrapper_2a_v, + conv_from_str>::def_wrap(ctx_cls, "copyBelPorts"); + +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getBelType"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getBelLocation"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "checkBelAvail"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getBelChecksum"); +fn_wrapper_3a_v, + addr_and_unwrap, pass_through>::def_wrap(ctx_cls, "bindBel"); +fn_wrapper_1a_v>::def_wrap( + ctx_cls, "unbindBel"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getBoundBelCell"); +fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getConflictingBelCell"); +fn_wrapper_0a>::def_wrap(ctx_cls, + "getBels"); + +fn_wrapper_2a, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "getBelPinWire"); +fn_wrapper_2a, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "getBelPinType"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getWireBelPins"); + +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getWireChecksum"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getWireType"); +fn_wrapper_3a_v, + addr_and_unwrap, pass_through>::def_wrap(ctx_cls, "bindWire"); +fn_wrapper_1a_v>::def_wrap( + ctx_cls, "unbindWire"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "checkWireAvail"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getBoundWireNet"); +fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getConflictingWireNet"); + +fn_wrapper_0a>::def_wrap(ctx_cls, + "getWires"); + +fn_wrapper_0a>::def_wrap(ctx_cls, + "getPips"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getPipChecksum"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getPipLocation"); +fn_wrapper_3a_v, addr_and_unwrap, + pass_through>::def_wrap(ctx_cls, "bindPip"); +fn_wrapper_1a_v>::def_wrap( + ctx_cls, "unbindPip"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "checkPipAvail"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getBoundPipNet"); +fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getConflictingPipNet"); + +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getPipsDownhill"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getPipsUphill"); + +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getPipSrcWire"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getPipDstWire"); +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getPipDelay"); + +fn_wrapper_0a>::def_wrap( + ctx_cls, "getChipName"); +fn_wrapper_0a>::def_wrap(ctx_cls, + "archId"); + +fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "writeSVG"); + +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "isBelLocationValid"); + +// const\_range\ getBelBuckets() const +fn_wrapper_0a>::def_wrap(ctx_cls, "getBelBuckets"); +// BelBucketId getBelBucketForBel(BelId bel) const +fn_wrapper_1a, + conv_from_str>::def_wrap(ctx_cls, "getBelBucketForBel"); +// BelBucketId getBelBucketForCellType(IdString cell\_type) const +fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getBelBucketForCellType"); +// const\_range\ getBelsInBucket(BelBucketId bucket) const +fn_wrapper_1a, conv_from_str>::def_wrap(ctx_cls, "getBelsInBucket"); +// bool isValidBelForCellType(IdString cell\_type, BelId bel) const +fn_wrapper_2a, + conv_from_str, conv_from_str>::def_wrap(ctx_cls, "isValidBelForCellType"); diff --git a/common/kernel/archcheck.cc b/common/kernel/archcheck.cc new file mode 100644 index 00000000..23ec7aee --- /dev/null +++ b/common/kernel/archcheck.cc @@ -0,0 +1,408 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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" + +#if 0 +#define dbg(...) log(__VA_ARGS__) +#else +#define dbg(...) +#endif + +USING_NEXTPNR_NAMESPACE + +#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 +#endif + +namespace { + +void archcheck_names(const Context *ctx) +{ + log_info("Checking entity names.\n"); + + log_info("Checking bel names..\n"); + for (BelId bel : ctx->getBels()) { + IdStringList name = ctx->getBelName(bel); + BelId bel2 = ctx->getBelByName(name); + if (bel != bel2) { + log_error("bel != bel2, name = %s\n", ctx->nameOfBel(bel)); + } + } + + log_info("Checking wire names..\n"); + for (WireId wire : ctx->getWires()) { + IdStringList name = ctx->getWireName(wire); + WireId wire2 = ctx->getWireByName(name); + if (wire != wire2) { + log_error("wire != wire2, name = %s\n", ctx->nameOfWire(wire)); + } + } + + log_info("Checking bucket names..\n"); + for (BelBucketId bucket : ctx->getBelBuckets()) { + IdString name = ctx->getBelBucketName(bucket); + BelBucketId bucket2 = ctx->getBelBucketByName(name); + if (bucket != bucket2) { + log_error("bucket != bucket2, name = %s\n", name.c_str(ctx)); + } + } + +#ifndef ARCH_ECP5 + log_info("Checking pip names..\n"); + for (PipId pip : ctx->getPips()) { + IdStringList name = ctx->getPipName(pip); + PipId pip2 = ctx->getPipByName(name); + if (pip != pip2) { + log_error("pip != pip2, name = %s\n", ctx->nameOfPip(pip)); + } + } +#endif + log_break(); +} + +void archcheck_locs(const Context *ctx) +{ + log_info("Checking location data.\n"); + + log_info("Checking all bels..\n"); + for (BelId bel : ctx->getBels()) { + log_assert(bel != BelId()); + dbg("> %s\n", ctx->getBelName(bel).c_str(ctx)); + + Loc loc = ctx->getBelLocation(bel); + dbg(" ... %d %d %d\n", loc.x, loc.y, loc.z); + + log_assert(0 <= loc.x); + log_assert(0 <= loc.y); + log_assert(0 <= loc.z); + log_assert(loc.x < ctx->getGridDimX()); + log_assert(loc.y < ctx->getGridDimY()); + log_assert(loc.z < ctx->getTileBelDimZ(loc.x, loc.y)); + + BelId bel2 = ctx->getBelByLocation(loc); + dbg(" ... %s\n", ctx->getBelName(bel2).c_str(ctx)); + log_assert(bel == bel2); + } + + log_info("Checking all locations..\n"); + for (int x = 0; x < ctx->getGridDimX(); x++) + for (int y = 0; y < ctx->getGridDimY(); y++) { + dbg("> %d %d\n", x, y); + pool usedz; + + for (int z = 0; z < ctx->getTileBelDimZ(x, y); z++) { + BelId bel = ctx->getBelByLocation(Loc(x, y, z)); + if (bel == BelId()) + continue; + Loc loc = ctx->getBelLocation(bel); + dbg(" + %d %s\n", z, ctx->nameOfBel(bel)); + log_assert(x == loc.x); + log_assert(y == loc.y); + log_assert(z == loc.z); + usedz.insert(z); + } + + for (BelId bel : ctx->getBelsByTile(x, y)) { + Loc loc = ctx->getBelLocation(bel); + dbg(" - %d %s\n", loc.z, ctx->nameOfBel(bel)); + log_assert(x == loc.x); + log_assert(y == loc.y); + log_assert(usedz.count(loc.z)); + usedz.erase(loc.z); + } + + log_assert(usedz.empty()); + } + + log_break(); +} + +// Implements a LRU cache for pip to wire via getPipsDownhill/getPipsUphill. +// +// This allows a fast way to check getPipsDownhill/getPipsUphill from getPips, +// without balloning memory usage. +struct LruWireCacheMap +{ + LruWireCacheMap(const Context *ctx, size_t cache_size) : ctx(ctx), cache_size(cache_size) + { + cache_hits = 0; + cache_misses = 0; + cache_evictions = 0; + } + + const Context *ctx; + size_t cache_size; + + // Cache stats for checking on cache behavior. + size_t cache_hits; + size_t cache_misses; + size_t cache_evictions; + + // Most recent accessed wires are added to the back of the list, front of + // list is oldest wire in cache. + std::list last_access_list; + // Quick wire -> list element lookup. + dict::iterator> last_access_map; + + dict pips_downhill; + dict pips_uphill; + + void removeWireFromCache(WireId wire_to_remove) + { + for (PipId pip : ctx->getPipsDownhill(wire_to_remove)) { + log_assert(pips_downhill.erase(pip) == 1); + } + + for (PipId pip : ctx->getPipsUphill(wire_to_remove)) { + log_assert(pips_uphill.erase(pip) == 1); + } + } + + void addWireToCache(WireId wire) + { + for (PipId pip : ctx->getPipsDownhill(wire)) { + auto result = pips_downhill.emplace(pip, wire); + log_assert(result.second); + } + + for (PipId pip : ctx->getPipsUphill(wire)) { + auto result = pips_uphill.emplace(pip, wire); + log_assert(result.second); + } + } + + void populateCache(WireId wire) + { + // Put this wire at the end of last_access_list. + auto iter = last_access_list.emplace(last_access_list.end(), wire); + last_access_map.emplace(wire, iter); + + if (last_access_list.size() > cache_size) { + // Cache is full, remove front of last_access_list. + cache_evictions += 1; + WireId wire_to_remove = last_access_list.front(); + last_access_list.pop_front(); + log_assert(last_access_map.erase(wire_to_remove) == 1); + + removeWireFromCache(wire_to_remove); + } + + addWireToCache(wire); + } + + // Determine if wire is in the cache. If wire is not in the cache, + // adds the wire to the cache, and potentially evicts the oldest wire if + // cache is now full. + void checkCache(WireId wire) + { + auto iter = last_access_map.find(wire); + if (iter == last_access_map.end()) { + cache_misses += 1; + populateCache(wire); + } else { + // Record that this wire has been accessed. + cache_hits += 1; + last_access_list.splice(last_access_list.end(), last_access_list, iter->second); + } + } + + // Returns true if pip is uphill of wire (e.g. pip in getPipsUphill(wire)). + bool isPipUphill(PipId pip, WireId wire) + { + checkCache(wire); + return pips_uphill.at(pip) == wire; + } + + // Returns true if pip is downhill of wire (e.g. pip in getPipsDownhill(wire)). + bool isPipDownhill(PipId pip, WireId wire) + { + checkCache(wire); + return pips_downhill.at(pip) == wire; + } + + void cache_info() const + { + log_info("Cache hits: %zu\n", cache_hits); + log_info("Cache misses: %zu\n", cache_misses); + log_info("Cache evictions: %zu\n", cache_evictions); + } +}; + +void archcheck_conn(const Context *ctx) +{ + log_info("Checking connectivity data.\n"); + + log_info("Checking all wires...\n"); + +#ifndef USING_LRU_CACHE + dict pips_downhill; + dict pips_uphill; +#endif + + for (WireId wire : ctx->getWires()) { + for (BelPin belpin : ctx->getWireBelPins(wire)) { + WireId wire2 = ctx->getBelPinWire(belpin.bel, belpin.pin); + log_assert(wire == wire2); + } + + 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 + } + } + + log_info("Checking all BELs...\n"); + for (BelId bel : ctx->getBels()) { + for (IdString pin : ctx->getBelPins(bel)) { + WireId wire = ctx->getBelPinWire(bel, pin); + + if (wire == WireId()) { + continue; + } + + bool found_belpin = false; + for (BelPin belpin : ctx->getWireBelPins(wire)) { + if (belpin.bel == bel && belpin.pin == pin) { + found_belpin = true; + break; + } + } + + 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. + // + // The overhead of maintaining the cache is small relatively to the memory + // gains by avoiding the full pip -> wire map, and still preserves a fast + // 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 + } + } +} + +void archcheck_buckets(const Context *ctx) +{ + log_info("Checking bucket data.\n"); + + // BEL buckets should be subsets of BELs that form an exact cover. + // In particular that means cell types in a bucket should only be + // placable in that bucket. + for (BelBucketId bucket : ctx->getBelBuckets()) { + + // Find out which cell types are in this bucket. + pool cell_types_in_bucket; + for (IdString cell_type : ctx->getCellTypes()) { + if (ctx->getBelBucketForCellType(cell_type) == bucket) { + cell_types_in_bucket.insert(cell_type); + } + } + + // Make sure that all cell types in this bucket have at least one + // BelId they can be placed at. + pool cell_types_unused; + + pool bels_in_bucket; + for (BelId bel : ctx->getBelsInBucket(bucket)) { + BelBucketId bucket2 = ctx->getBelBucketForBel(bel); + log_assert(bucket == bucket2); + + bels_in_bucket.insert(bel); + + // Check to see if a cell type not in this bucket can be + // placed at a BEL in this bucket. + for (IdString cell_type : ctx->getCellTypes()) { + if (ctx->getBelBucketForCellType(cell_type) == bucket) { + if (ctx->isValidBelForCellType(cell_type, bel)) { + cell_types_unused.erase(cell_type); + } + } else { + log_assert(!ctx->isValidBelForCellType(cell_type, bel)); + } + } + } + + // Verify that any BEL not in this bucket reports a different + // bucket. + for (BelId bel : ctx->getBels()) { + if (ctx->getBelBucketForBel(bel) != bucket) { + log_assert(bels_in_bucket.count(bel) == 0); + } + } + + log_assert(cell_types_unused.empty()); + } +} + +} // namespace + +NEXTPNR_NAMESPACE_BEGIN + +void Context::archcheck() const +{ + log_info("Running architecture database integrity check.\n"); + log_break(); + + archcheck_names(this); + archcheck_locs(this); + archcheck_conn(this); + archcheck_buckets(this); +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/base_arch.h b/common/kernel/base_arch.h new file mode 100644 index 00000000..3055619d --- /dev/null +++ b/common/kernel/base_arch.h @@ -0,0 +1,486 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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 BASE_ARCH_H +#define BASE_ARCH_H + +#include +#include + +#include "arch_api.h" +#include "base_clusterinfo.h" +#include "idstring.h" +#include "nextpnr_types.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +// For several functions; such as bel/wire/pip attributes; the trivial implementation is to return an empty vector +// But an arch might want to do something fancy with a custom range type that doesn't provide a constructor +// So some cursed C++ is needed to return an empty object if possible; or error out if not; is needed +template typename std::enable_if::value, Tc>::type empty_if_possible() +{ + return Tc(); +} +template typename std::enable_if::value, Tc>::type empty_if_possible() +{ + NPNR_ASSERT_FALSE("attempting to use default implementation of range-returning function with range type lacking " + "default constructor!"); +} + +// Provide a default implementation of bel bucket name if typedef'd to IdString +template +typename std::enable_if::value, IdString>::type bbid_to_name(Tbbid id) +{ + return id; +} +template +typename std::enable_if::value, IdString>::type bbid_to_name(Tbbid id) +{ + NPNR_ASSERT_FALSE("getBelBucketName must be implemented when BelBucketId is a type other than IdString!"); +} +template +typename std::enable_if::value, BelBucketId>::type bbid_from_name(IdString name) +{ + return name; +} +template +typename std::enable_if::value, BelBucketId>::type bbid_from_name(IdString name) +{ + NPNR_ASSERT_FALSE("getBelBucketByName must be implemented when BelBucketId is a type other than IdString!"); +} + +// For the cell type and bel type ranges; we want to return our stored vectors only if the type matches +template +typename std::enable_if::value, Tret>::type return_if_match(Tret r) +{ + return r; +} + +template +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&'"); +} + +// Default implementations of the clustering functions +template +typename std::enable_if::value, CellInfo *>::type get_cluster_root(const BaseCtx *ctx, + Tid cluster) +{ + return ctx->cells.at(cluster).get(); +} + +template +typename std::enable_if::value, CellInfo *>::type get_cluster_root(const BaseCtx *ctx, + Tid cluster) +{ + NPNR_ASSERT_FALSE("default implementation of getClusterRootCell requires ClusterId to be IdString"); +} + +// Executes the lambda with the base cluster data, only if the derivation works +template +typename std::enable_if::value, Tret>::type +if_using_basecluster(const Tcell *cell, Tfunc func) +{ + return func(static_cast(cell)); +} +template +typename std::enable_if::value, Tret>::type +if_using_basecluster(const Tcell *cell, Tfunc func) +{ + NPNR_ASSERT_FALSE( + "default implementation of cluster functions requires ArchCellInfo to derive from BaseClusterInfo"); +} + +} // namespace + +// This contains the relevant range types for the default implementations of Arch functions +struct BaseArchRanges +{ + // Bels + using CellBelPinRangeT = std::array; + // Attributes + using BelAttrsRangeT = std::vector>; + using WireAttrsRangeT = std::vector>; + using PipAttrsRangeT = std::vector>; + // Groups + using AllGroupsRangeT = std::vector; + using GroupBelsRangeT = std::vector; + using GroupWiresRangeT = std::vector; + using GroupPipsRangeT = std::vector; + using GroupGroupsRangeT = std::vector; + // Decals + using DecalGfxRangeT = std::vector; + // Placement validity + using CellTypeRangeT = const std::vector &; + using BelBucketRangeT = const std::vector &; + using BucketBelRangeT = const std::vector &; +}; + +template struct BaseArch : ArchAPI +{ + // -------------------------------------------------------------- + // Default, trivial, implementations of Arch API functions for arches that don't need complex behaviours + + // Basic config + virtual IdString archId() const override { return this->id(NPNR_STRINGIFY(ARCHNAME)); } + virtual IdString archArgsToId(typename R::ArchArgsT args) const override { return IdString(); } + virtual int getTilePipDimZ(int x, int y) const override { return 1; } + virtual char getNameDelimiter() const override { return ' '; } + + // Bel methods + virtual uint32_t getBelChecksum(BelId bel) const override { return bel.hash(); } + virtual void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) override + { + NPNR_ASSERT(bel != BelId()); + auto &entry = base_bel2cell[bel]; + NPNR_ASSERT(entry == nullptr); + cell->bel = bel; + cell->belStrength = strength; + entry = cell; + this->refreshUiBel(bel); + } + virtual void unbindBel(BelId bel) override + { + NPNR_ASSERT(bel != BelId()); + auto &entry = base_bel2cell[bel]; + NPNR_ASSERT(entry != nullptr); + entry->bel = BelId(); + entry->belStrength = STRENGTH_NONE; + entry = nullptr; + this->refreshUiBel(bel); + } + + virtual bool getBelHidden(BelId bel) const override { return false; } + + virtual bool getBelGlobalBuf(BelId bel) const override { return false; } + virtual bool checkBelAvail(BelId bel) const override { return getBoundBelCell(bel) == nullptr; }; + virtual CellInfo *getBoundBelCell(BelId bel) const override + { + auto fnd = base_bel2cell.find(bel); + return fnd == base_bel2cell.end() ? nullptr : fnd->second; + } + virtual CellInfo *getConflictingBelCell(BelId bel) const override { return getBoundBelCell(bel); } + virtual typename R::BelAttrsRangeT getBelAttrs(BelId bel) const override + { + return empty_if_possible(); + } + + virtual typename R::CellBelPinRangeT getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const override + { + return return_if_match, typename R::CellBelPinRangeT>({pin}); + } + + // Wire methods + virtual IdString getWireType(WireId wire) const override { return IdString(); } + virtual typename R::WireAttrsRangeT getWireAttrs(WireId) const override + { + return empty_if_possible(); + } + virtual uint32_t getWireChecksum(WireId wire) const override { return wire.hash(); } + + virtual void bindWire(WireId wire, NetInfo *net, PlaceStrength strength) override + { + NPNR_ASSERT(wire != WireId()); + auto &w2n_entry = base_wire2net[wire]; + NPNR_ASSERT(w2n_entry == nullptr); + net->wires[wire].pip = PipId(); + net->wires[wire].strength = strength; + w2n_entry = net; + this->refreshUiWire(wire); + } + virtual void unbindWire(WireId wire) override + { + NPNR_ASSERT(wire != WireId()); + auto &w2n_entry = base_wire2net[wire]; + NPNR_ASSERT(w2n_entry != nullptr); + + auto &net_wires = w2n_entry->wires; + auto it = net_wires.find(wire); + NPNR_ASSERT(it != net_wires.end()); + + auto pip = it->second.pip; + if (pip != PipId()) { + base_pip2net[pip] = nullptr; + } + + net_wires.erase(it); + base_wire2net[wire] = nullptr; + + w2n_entry = nullptr; + this->refreshUiWire(wire); + } + virtual bool checkWireAvail(WireId wire) const override { return getBoundWireNet(wire) == nullptr; } + virtual NetInfo *getBoundWireNet(WireId wire) const override + { + auto fnd = base_wire2net.find(wire); + return fnd == base_wire2net.end() ? nullptr : fnd->second; + } + virtual WireId getConflictingWireWire(WireId wire) const override { return wire; }; + virtual NetInfo *getConflictingWireNet(WireId wire) const override { return getBoundWireNet(wire); } + + // Pip methods + virtual IdString getPipType(PipId pip) const override { return IdString(); } + virtual typename R::PipAttrsRangeT getPipAttrs(PipId) const override + { + return empty_if_possible(); + } + virtual uint32_t getPipChecksum(PipId pip) const override { return pip.hash(); } + virtual void bindPip(PipId pip, NetInfo *net, PlaceStrength strength) override + { + NPNR_ASSERT(pip != PipId()); + auto &p2n_entry = base_pip2net[pip]; + NPNR_ASSERT(p2n_entry == nullptr); + p2n_entry = net; + + WireId dst = this->getPipDstWire(pip); + auto &w2n_entry = base_wire2net[dst]; + NPNR_ASSERT(w2n_entry == nullptr); + w2n_entry = net; + net->wires[dst].pip = pip; + net->wires[dst].strength = strength; + } + virtual void unbindPip(PipId pip) override + { + NPNR_ASSERT(pip != PipId()); + auto &p2n_entry = base_pip2net[pip]; + NPNR_ASSERT(p2n_entry != nullptr); + WireId dst = this->getPipDstWire(pip); + + auto &w2n_entry = base_wire2net[dst]; + NPNR_ASSERT(w2n_entry != nullptr); + w2n_entry = nullptr; + + p2n_entry->wires.erase(dst); + p2n_entry = nullptr; + } + virtual bool checkPipAvail(PipId pip) const override { return getBoundPipNet(pip) == nullptr; } + virtual bool checkPipAvailForNet(PipId pip, NetInfo *net) const override + { + NetInfo *bound_net = getBoundPipNet(pip); + return bound_net == nullptr || bound_net == net; + } + virtual NetInfo *getBoundPipNet(PipId pip) const override + { + auto fnd = base_pip2net.find(pip); + return fnd == base_pip2net.end() ? nullptr : fnd->second; + } + virtual WireId getConflictingPipWire(PipId pip) const override { return WireId(); } + virtual NetInfo *getConflictingPipNet(PipId pip) const override { return getBoundPipNet(pip); } + + // Group methods + virtual GroupId getGroupByName(IdStringList name) const override { return GroupId(); }; + virtual IdStringList getGroupName(GroupId group) const override { return IdStringList(); }; + virtual typename R::AllGroupsRangeT getGroups() const override + { + return empty_if_possible(); + } + // Default implementation of these assumes no groups so never called + virtual typename R::GroupBelsRangeT getGroupBels(GroupId group) const override + { + NPNR_ASSERT_FALSE("unreachable"); + }; + virtual typename R::GroupWiresRangeT getGroupWires(GroupId group) const override + { + NPNR_ASSERT_FALSE("unreachable"); + }; + virtual typename R::GroupPipsRangeT getGroupPips(GroupId group) const override + { + NPNR_ASSERT_FALSE("unreachable"); + }; + virtual typename R::GroupGroupsRangeT getGroupGroups(GroupId group) const override + { + NPNR_ASSERT_FALSE("unreachable"); + }; + + // Delay methods + virtual bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const override + { + return false; + } + + // Decal methods + virtual typename R::DecalGfxRangeT getDecalGraphics(DecalId decal) const override + { + return empty_if_possible(); + }; + virtual DecalXY getBelDecal(BelId bel) const override { return DecalXY(); } + virtual DecalXY getWireDecal(WireId wire) const override { return DecalXY(); } + virtual DecalXY getPipDecal(PipId pip) const override { return DecalXY(); } + virtual DecalXY getGroupDecal(GroupId group) const override { return DecalXY(); } + + // Cell timing methods + virtual bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const override + { + return false; + } + virtual TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const override + { + return TMG_IGNORE; + } + virtual TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const override + { + NPNR_ASSERT_FALSE("unreachable"); + } + + // Placement validity checks + virtual bool isValidBelForCellType(IdString cell_type, BelId bel) const override + { + return cell_type == this->getBelType(bel); + } + virtual IdString getBelBucketName(BelBucketId bucket) const override { return bbid_to_name(bucket); } + virtual BelBucketId getBelBucketByName(IdString name) const override { return bbid_from_name(name); } + virtual BelBucketId getBelBucketForBel(BelId bel) const override + { + return getBelBucketForCellType(this->getBelType(bel)); + }; + virtual BelBucketId getBelBucketForCellType(IdString cell_type) const override + { + return getBelBucketByName(cell_type); + }; + virtual bool isBelLocationValid(BelId bel) const override { return true; } + virtual typename R::CellTypeRangeT getCellTypes() const override + { + NPNR_ASSERT(cell_types_initialised); + return return_if_match &, typename R::CellTypeRangeT>(cell_types); + } + virtual typename R::BelBucketRangeT getBelBuckets() const override + { + NPNR_ASSERT(bel_buckets_initialised); + return return_if_match &, typename R::BelBucketRangeT>(bel_buckets); + } + virtual typename R::BucketBelRangeT getBelsInBucket(BelBucketId bucket) const override + { + NPNR_ASSERT(bel_buckets_initialised); + return return_if_match &, typename R::BucketBelRangeT>(bucket_bels.at(bucket)); + } + + // Cluster methods + virtual CellInfo *getClusterRootCell(ClusterId cluster) const override { return get_cluster_root(this, cluster); } + + virtual ArcBounds getClusterBounds(ClusterId cluster) const override + { + return if_using_basecluster(get_cluster_root(this, cluster), [](const BaseClusterInfo *cluster) { + ArcBounds bounds(0, 0, 0, 0); + for (auto child : cluster->constr_children) { + if_using_basecluster(child, [&](const BaseClusterInfo *child) { + bounds.x0 = std::min(bounds.x0, child->constr_x); + bounds.y0 = std::min(bounds.y0, child->constr_y); + bounds.x1 = std::max(bounds.x1, child->constr_x); + bounds.y1 = std::max(bounds.y1, child->constr_y); + }); + } + return bounds; + }); + } + + virtual Loc getClusterOffset(const CellInfo *cell) const override + { + return if_using_basecluster(cell, + [](const BaseClusterInfo *c) { return Loc(c->constr_x, c->constr_y, 0); }); + } + + virtual bool isClusterStrict(const CellInfo *cell) const override { return true; } + + virtual bool getClusterPlacement(ClusterId cluster, BelId root_bel, + std::vector> &placement) const override + { + CellInfo *root_cell = get_cluster_root(this, cluster); + return if_using_basecluster(root_cell, [&](const BaseClusterInfo *cluster) -> bool { + placement.clear(); + NPNR_ASSERT(root_bel != BelId()); + Loc root_loc = this->getBelLocation(root_bel); + + if (cluster->constr_abs_z) { + // Coerce root to absolute z constraint + root_loc.z = cluster->constr_z; + root_bel = this->getBelByLocation(root_loc); + if (root_bel == BelId() || !this->isValidBelForCellType(root_cell->type, root_bel)) + return false; + } + placement.emplace_back(root_cell, root_bel); + + for (auto child : cluster->constr_children) { + Loc child_loc = if_using_basecluster(child, [&](const BaseClusterInfo *child) { + Loc result; + result.x = root_loc.x + child->constr_x; + result.y = root_loc.y + child->constr_y; + result.z = child->constr_abs_z ? child->constr_z : (root_loc.z + child->constr_z); + return result; + }); + BelId child_bel = this->getBelByLocation(child_loc); + if (child_bel == BelId() || !this->isValidBelForCellType(child->type, child_bel)) + return false; + placement.emplace_back(child, child_bel); + } + return true; + }); + } + + // Flow methods + virtual void assignArchInfo() override{}; + + // -------------------------------------------------------------- + // These structures are used to provide default implementations of bel/wire/pip binding. Arches might want to + // replace them with their own, for example to use faster access structures than dict. Arches might also + // want to add extra checks around these functions + dict base_bel2cell; + dict base_wire2net; + dict base_pip2net; + + // For the default cell/bel bucket implementations + std::vector cell_types; + std::vector bel_buckets; + dict> bucket_bels; + + // Arches that want to use the default cell types and bel buckets *must* call these functions in their constructor + bool cell_types_initialised = false; + bool bel_buckets_initialised = false; + void init_cell_types() + { + pool bel_types; + for (auto bel : this->getBels()) + bel_types.insert(this->getBelType(bel)); + std::copy(bel_types.begin(), bel_types.end(), std::back_inserter(cell_types)); + std::sort(cell_types.begin(), cell_types.end()); + cell_types_initialised = true; + } + void init_bel_buckets() + { + for (auto cell_type : this->getCellTypes()) { + auto bucket = this->getBelBucketForCellType(cell_type); + bucket_bels[bucket]; // create empty bucket + } + for (auto bel : this->getBels()) { + auto bucket = this->getBelBucketForBel(bel); + bucket_bels[bucket].push_back(bel); + } + for (auto &b : bucket_bels) + bel_buckets.push_back(b.first); + std::sort(bel_buckets.begin(), bel_buckets.end()); + bel_buckets_initialised = true; + } +}; + +NEXTPNR_NAMESPACE_END + +#endif /* BASE_ARCH_H */ diff --git a/common/kernel/base_clusterinfo.h b/common/kernel/base_clusterinfo.h new file mode 100644 index 00000000..65e8e6d4 --- /dev/null +++ b/common/kernel/base_clusterinfo.h @@ -0,0 +1,45 @@ +/* + * 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. + * + */ + +#ifndef BASE_CLUSTERINFO_H +#define BASE_CLUSTERINFO_H + +#include "idstring.h" +#include "nextpnr_namespaces.h" + +#include + +NEXTPNR_NAMESPACE_BEGIN + +struct CellInfo; + +// The 'legacy' cluster data, used for existing arches and to provide a basic implementation for arches without complex +// clustering requirements +struct BaseClusterInfo +{ + std::vector constr_children; + int constr_x = 0; // this.x - parent.x + int constr_y = 0; // this.y - parent.y + int constr_z = 0; // this.z - parent.z + bool constr_abs_z = false; // parent.z := 0 +}; + +NEXTPNR_NAMESPACE_END + +#endif /* BASE_ARCH_H */ diff --git a/common/kernel/basectx.cc b/common/kernel/basectx.cc new file mode 100644 index 00000000..83a2deea --- /dev/null +++ b/common/kernel/basectx.cc @@ -0,0 +1,279 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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 "basectx.h" + +#include + +#include "context.h" +#include "log.h" + +NEXTPNR_NAMESPACE_BEGIN + +const char *BaseCtx::nameOfBel(BelId bel) const +{ + const Context *ctx = getCtx(); + std::string &s = ctx->log_strs.next(); + ctx->getBelName(bel).build_str(ctx, s); + return s.c_str(); +} + +const char *BaseCtx::nameOfWire(WireId wire) const +{ + const Context *ctx = getCtx(); + std::string &s = ctx->log_strs.next(); + ctx->getWireName(wire).build_str(ctx, s); + return s.c_str(); +} + +const char *BaseCtx::nameOfPip(PipId pip) const +{ + const Context *ctx = getCtx(); + std::string &s = ctx->log_strs.next(); + ctx->getPipName(pip).build_str(ctx, s); + return s.c_str(); +} + +const char *BaseCtx::nameOfGroup(GroupId group) const +{ + const Context *ctx = getCtx(); + std::string &s = ctx->log_strs.next(); + ctx->getGroupName(group).build_str(ctx, s); + return s.c_str(); +} + +BelId BaseCtx::getBelByNameStr(const std::string &str) +{ + Context *ctx = getCtx(); + return ctx->getBelByName(IdStringList::parse(ctx, str)); +} + +WireId BaseCtx::getWireByNameStr(const std::string &str) +{ + Context *ctx = getCtx(); + return ctx->getWireByName(IdStringList::parse(ctx, str)); +} + +PipId BaseCtx::getPipByNameStr(const std::string &str) +{ + Context *ctx = getCtx(); + return ctx->getPipByName(IdStringList::parse(ctx, str)); +} + +GroupId BaseCtx::getGroupByNameStr(const std::string &str) +{ + Context *ctx = getCtx(); + return ctx->getGroupByName(IdStringList::parse(ctx, str)); +} + +void BaseCtx::addClock(IdString net, float freq) +{ + std::unique_ptr cc(new ClockConstraint()); + cc->period = DelayPair(getCtx()->getDelayFromNS(1000 / freq)); + cc->high = DelayPair(getCtx()->getDelayFromNS(500 / freq)); + cc->low = DelayPair(getCtx()->getDelayFromNS(500 / freq)); + if (!net_aliases.count(net)) { + log_warning("net '%s' does not exist in design, ignoring clock constraint\n", net.c_str(this)); + } else { + getNetByAlias(net)->clkconstr = std::move(cc); + log_info("constraining clock net '%s' to %.02f MHz\n", net.c_str(this), freq); + } +} + +void BaseCtx::createRectangularRegion(IdString name, int x0, int y0, int x1, int y1) +{ + std::unique_ptr new_region(new Region()); + new_region->name = name; + new_region->constr_bels = true; + new_region->constr_pips = false; + new_region->constr_wires = false; + for (int x = x0; x <= x1; x++) { + for (int y = y0; y <= y1; y++) { + for (auto bel : getCtx()->getBelsByTile(x, y)) + new_region->bels.insert(bel); + } + } + region[name] = std::move(new_region); +} +void BaseCtx::addBelToRegion(IdString name, BelId bel) { region[name]->bels.insert(bel); } +void BaseCtx::constrainCellToRegion(IdString cell, IdString region_name) +{ + // Support hierarchical cells as well as leaf ones + bool matched = false; + if (hierarchy.count(cell)) { + auto &hc = hierarchy.at(cell); + for (auto &lc : hc.leaf_cells) + constrainCellToRegion(lc.second, region_name); + for (auto &hsc : hc.hier_cells) + constrainCellToRegion(hsc.second, region_name); + matched = true; + } + if (cells.count(cell)) { + cells.at(cell)->region = region[region_name].get(); + matched = true; + } + if (!matched) + log_warning("No cell matched '%s' when constraining to region '%s'\n", nameOf(cell), nameOf(region_name)); +} +DecalXY BaseCtx::constructDecalXY(DecalId decal, float x, float y) +{ + DecalXY dxy; + dxy.decal = decal; + dxy.x = x; + dxy.y = y; + return dxy; +} + +void BaseCtx::archInfoToAttributes() +{ + for (auto &cell : cells) { + auto ci = cell.second.get(); + if (ci->bel != BelId()) { + if (ci->attrs.find(id("BEL")) != ci->attrs.end()) { + ci->attrs.erase(ci->attrs.find(id("BEL"))); + } + ci->attrs[id("NEXTPNR_BEL")] = getCtx()->getBelName(ci->bel).str(getCtx()); + ci->attrs[id("BEL_STRENGTH")] = (int)ci->belStrength; + } + } + for (auto &net : getCtx()->nets) { + auto ni = net.second.get(); + std::string routing; + bool first = true; + for (auto &item : ni->wires) { + if (!first) + routing += ";"; + routing += getCtx()->getWireName(item.first).str(getCtx()); + routing += ";"; + if (item.second.pip != PipId()) + routing += getCtx()->getPipName(item.second.pip).str(getCtx()); + routing += ";" + std::to_string(item.second.strength); + first = false; + } + ni->attrs[id("ROUTING")] = routing; + } +} + +void BaseCtx::attributesToArchInfo() +{ + for (auto &cell : cells) { + auto ci = cell.second.get(); + auto val = ci->attrs.find(id("NEXTPNR_BEL")); + if (val != ci->attrs.end()) { + auto str = ci->attrs.find(id("BEL_STRENGTH")); + PlaceStrength strength = PlaceStrength::STRENGTH_USER; + if (str != ci->attrs.end()) + strength = (PlaceStrength)str->second.as_int64(); + + BelId b = getCtx()->getBelByNameStr(val->second.as_string()); + getCtx()->bindBel(b, ci, strength); + } + } + for (auto &net : getCtx()->nets) { + auto ni = net.second.get(); + auto val = ni->attrs.find(id("ROUTING")); + if (val != ni->attrs.end()) { + std::vector strs; + auto routing = val->second.as_string(); + boost::split(strs, routing, boost::is_any_of(";")); + for (size_t i = 0; i < strs.size() / 3; i++) { + std::string wire = strs[i * 3]; + std::string pip = strs[i * 3 + 1]; + PlaceStrength strength = (PlaceStrength)std::stoi(strs[i * 3 + 2]); + if (pip.empty()) + getCtx()->bindWire(getCtx()->getWireByName(IdStringList::parse(getCtx(), wire)), ni, strength); + else + getCtx()->bindPip(getCtx()->getPipByName(IdStringList::parse(getCtx(), pip)), ni, strength); + } + } + } + getCtx()->assignArchInfo(); +} + +NetInfo *BaseCtx::createNet(IdString name) +{ + NPNR_ASSERT(!nets.count(name)); + NPNR_ASSERT(!net_aliases.count(name)); + auto net = std::make_unique(name); + net_aliases[name] = name; + NetInfo *ptr = net.get(); + nets[name] = std::move(net); + refreshUi(); + return ptr; +} + +void BaseCtx::connectPort(IdString net, IdString cell, IdString port) +{ + NetInfo *net_info = getNetByAlias(net); + CellInfo *cell_info = cells.at(cell).get(); + cell_info->connectPort(port, net_info); +} + +void BaseCtx::disconnectPort(IdString cell, IdString port) +{ + CellInfo *cell_info = cells.at(cell).get(); + cell_info->disconnectPort(port); +} + +void BaseCtx::renameNet(IdString old_name, IdString new_name) +{ + NetInfo *net = nets.at(old_name).get(); + NPNR_ASSERT(!nets.count(new_name)); + nets[new_name]; + std::swap(nets.at(net->name), nets.at(new_name)); + nets.erase(net->name); + net->name = new_name; +} + +void BaseCtx::ripupNet(IdString name) +{ + NetInfo *net_info = getNetByAlias(name); + std::vector to_unbind; + for (auto &wire : net_info->wires) + to_unbind.push_back(wire.first); + for (auto &unbind : to_unbind) + getCtx()->unbindWire(unbind); +} +void BaseCtx::lockNetRouting(IdString name) +{ + NetInfo *net_info = getNetByAlias(name); + for (auto &wire : net_info->wires) + wire.second.strength = STRENGTH_USER; +} + +CellInfo *BaseCtx::createCell(IdString name, IdString type) +{ + NPNR_ASSERT(!cells.count(name)); + auto cell = std::make_unique(getCtx(), name, type); + CellInfo *ptr = cell.get(); + cells[name] = std::move(cell); + refreshUi(); + return ptr; +} + +void BaseCtx::copyBelPorts(IdString cell, BelId bel) +{ + CellInfo *cell_info = cells.at(cell).get(); + for (auto pin : getCtx()->getBelPins(bel)) { + cell_info->ports[pin].name = pin; + cell_info->ports[pin].type = getCtx()->getBelPinType(bel, pin); + } +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/basectx.h b/common/kernel/basectx.h new file mode 100644 index 00000000..21d6d63a --- /dev/null +++ b/common/kernel/basectx.h @@ -0,0 +1,243 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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 BASECTX_H +#define BASECTX_H + +#include +#include +#include +#ifndef NPNR_DISABLE_THREADS +#include +#endif + +#include "hashlib.h" +#include "idstring.h" +#include "nextpnr_namespaces.h" +#include "nextpnr_types.h" +#include "property.h" +#include "str_ring_buffer.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct Context; + +struct BaseCtx +{ +#ifndef NPNR_DISABLE_THREADS + // Lock to perform mutating actions on the Context. + std::mutex mutex; + boost::thread::id mutex_owner; + + // Lock to be taken by UI when wanting to access context - the yield() + // method will lock/unlock it when its' released the main mutex to make + // sure the UI is not starved. + std::mutex ui_mutex; +#endif + + // ID String database. + mutable std::unordered_map *idstring_str_to_idx; + mutable std::vector *idstring_idx_to_str; + + // Temporary string backing store for logging + mutable StrRingBuffer log_strs; + + // Project settings and config switches + dict settings; + + // Placed nets and cells. + dict> nets; + dict> cells; + + // Hierarchical (non-leaf) cells by full path + dict hierarchy; + // This is the root of the above structure + IdString top_module; + + // Aliases for nets, which may have more than one name due to assignments and hierarchy + dict net_aliases; + + // Top-level ports + dict ports; + dict port_cells; + + // Floorplanning regions + dict> region; + + // Context meta data + dict attrs; + + // Fmax data post timing analysis + TimingResult timing_result; + + Context *as_ctx = nullptr; + + // Has the frontend loaded a design? + bool design_loaded; + + BaseCtx() + { + idstring_str_to_idx = new std::unordered_map; + idstring_idx_to_str = new std::vector; + IdString::initialize_add(this, "", 0); + IdString::initialize_arch(this); + + design_loaded = false; + } + + virtual ~BaseCtx() + { + delete idstring_str_to_idx; + delete idstring_idx_to_str; + } + + // Must be called before performing any mutating changes on the Ctx/Arch. + void lock(void) + { +#ifndef NPNR_DISABLE_THREADS + mutex.lock(); + mutex_owner = boost::this_thread::get_id(); +#endif + } + + void unlock(void) + { +#ifndef NPNR_DISABLE_THREADS + NPNR_ASSERT(boost::this_thread::get_id() == mutex_owner); + mutex.unlock(); +#endif + } + + // Must be called by the UI before rendering data. This lock will be + // prioritized when processing code calls yield(). + void lock_ui(void) + { +#ifndef NPNR_DISABLE_THREADS + ui_mutex.lock(); + mutex.lock(); +#endif + } + + void unlock_ui(void) + { +#ifndef NPNR_DISABLE_THREADS + mutex.unlock(); + ui_mutex.unlock(); +#endif + } + + // Yield to UI by unlocking the main mutex, flashing the UI mutex and + // relocking the main mutex. Call this when you're performing a + // long-standing action while holding a lock to let the UI show + // visualization updates. + // Must be called with the main lock taken. + void yield(void) + { +#ifndef NPNR_DISABLE_THREADS + unlock(); + ui_mutex.lock(); + ui_mutex.unlock(); + lock(); +#endif + } + + IdString id(const std::string &s) const { return IdString(this, s); } + + IdString id(const char *s) const { return IdString(this, s); } + + Context *getCtx() { return as_ctx; } + + const Context *getCtx() const { return as_ctx; } + + const char *nameOf(IdString name) const { return name.c_str(this); } + + template const char *nameOf(const T *obj) const + { + if (obj == nullptr) + return ""; + return obj->name.c_str(this); + } + + const char *nameOfBel(BelId bel) const; + const char *nameOfWire(WireId wire) const; + const char *nameOfPip(PipId pip) const; + const char *nameOfGroup(GroupId group) const; + + // Wrappers of arch functions that take a string and handle IdStringList parsing + BelId getBelByNameStr(const std::string &str); + WireId getWireByNameStr(const std::string &str); + PipId getPipByNameStr(const std::string &str); + GroupId getGroupByNameStr(const std::string &str); + + // -------------------------------------------------------------- + + bool allUiReload = true; + bool frameUiReload = false; + pool belUiReload; + pool wireUiReload; + pool pipUiReload; + pool groupUiReload; + + void refreshUi() { allUiReload = true; } + + void refreshUiFrame() { frameUiReload = true; } + + void refreshUiBel(BelId bel) { belUiReload.insert(bel); } + + void refreshUiWire(WireId wire) { wireUiReload.insert(wire); } + + void refreshUiPip(PipId pip) { pipUiReload.insert(pip); } + + void refreshUiGroup(GroupId group) { groupUiReload.insert(group); } + + // -------------------------------------------------------------- + + NetInfo *getNetByAlias(IdString alias) const + { + return nets.count(alias) ? nets.at(alias).get() : nets.at(net_aliases.at(alias)).get(); + } + + // Intended to simplify Python API + void addClock(IdString net, float freq); + void createRectangularRegion(IdString name, int x0, int y0, int x1, int y1); + void addBelToRegion(IdString name, BelId bel); + void constrainCellToRegion(IdString cell, IdString region_name); + + // Helper functions for Python bindings + NetInfo *createNet(IdString name); + void connectPort(IdString net, IdString cell, IdString port); + void disconnectPort(IdString cell, IdString port); + void ripupNet(IdString name); + void lockNetRouting(IdString name); + void renameNet(IdString old_name, IdString new_name); + + CellInfo *createCell(IdString name, IdString type); + void copyBelPorts(IdString cell, BelId bel); + + // Workaround for lack of wrappable constructors + DecalXY constructDecalXY(DecalId decal, float x, float y); + + void archInfoToAttributes(); + void attributesToArchInfo(); +}; + +NEXTPNR_NAMESPACE_END + +#endif /* BASECTX_H */ diff --git a/common/kernel/bits.cc b/common/kernel/bits.cc new file mode 100644 index 00000000..b20c2e86 --- /dev/null +++ b/common/kernel/bits.cc @@ -0,0 +1,56 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (c) 2013 Mike Pedersen + * Copyright (C) 2021 Symbiflow Authors + * + * 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 "bits.h" + +#include +#include + +#include "log.h" + +NEXTPNR_NAMESPACE_BEGIN + +int Bits::generic_popcount(unsigned int v) +{ + unsigned int c; // c accumulates the total bits set in v + for (c = 0; v; c++) { + v &= v - 1; // clear the least significant bit set + } + + return c; +} + +int Bits::generic_ctz(unsigned int x) +{ + if (x == 0) { + log_error("Cannot call ctz with arg = 0"); + } + + for (size_t i = 0; i < std::numeric_limits::digits; ++i) { + if ((x & (1 << i)) != 0) { + return i; + } + } + + // Unreachable! + log_error("Unreachable!"); +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/bits.h b/common/kernel/bits.h new file mode 100644 index 00000000..04b25b74 --- /dev/null +++ b/common/kernel/bits.h @@ -0,0 +1,76 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (c) 2013 Mike Pedersen + * Copyright (C) 2021 Symbiflow Authors + * + * 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. + * + */ + +// This is a small library for implementing common bit vector utilities, +// namely: +// +// - popcount : The number of bits set in an unsigned int +// - ctz : The number of trailing zero bits in an unsigned int. +// Must be called with a value that has at least 1 bit set. +// +// These methods will typically use instrinics when available, and have a +// generic fallback in the event that the instrinic is not available. +// +// If clz (count leading zeros) is needed, it can be added when needed. +#ifndef BITS_H +#define BITS_H + +#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) +#include +#pragma intrinsic(_BitScanForward, _BitScanReverse, __popcnt) +#endif + +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct Bits +{ + static int generic_popcount(unsigned int x); + static int generic_ctz(unsigned int x); + + static int popcount(unsigned int x) + { +#if defined(__GNUC__) || defined(__clang__) + return __builtin_popcount(x); +#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) + return __popcnt(x); +#else + return generic_popcount(x); +#endif + } + + static int ctz(unsigned int x) + { +#if defined(__GNUC__) || defined(__clang__) + return __builtin_ctz(x); +#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) + unsigned long result; + _BitScanForward(&result, x); + return result; +#else + return generic_ctz(x); +#endif + } +}; + +NEXTPNR_NAMESPACE_END + +#endif /* BITS_H */ diff --git a/common/kernel/chain_utils.h b/common/kernel/chain_utils.h new file mode 100644 index 00000000..ca8a1be3 --- /dev/null +++ b/common/kernel/chain_utils.h @@ -0,0 +1,69 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * 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 CHAIN_UTILS_H +#define CHAIN_UTILS_H + +#include "nextpnr.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct CellChain +{ + std::vector cells; +}; + +// Generic chain finder +template +std::vector find_chains(const Context *ctx, F1 cell_type_predicate, F2 get_previous, F3 get_next, + size_t min_length = 2) +{ + std::set chained; + std::vector chains; + for (auto &cell : ctx->cells) { + if (chained.find(cell.first) != chained.end()) + continue; + CellInfo *ci = cell.second.get(); + if (cell_type_predicate(ctx, ci)) { + CellInfo *start = ci; + CellInfo *prev_start = ci; + while (prev_start != nullptr) { + start = prev_start; + prev_start = get_previous(ctx, start); + } + CellChain chain; + CellInfo *end = start; + while (end != nullptr) { + if (chained.insert(end->name).second) + chain.cells.push_back(end); + end = get_next(ctx, end); + } + if (chain.cells.size() >= min_length) { + chains.push_back(chain); + for (auto c : chain.cells) + chained.insert(c->name); + } + } + } + return chains; +} + +NEXTPNR_NAMESPACE_END +#endif diff --git a/common/kernel/command.cc b/common/kernel/command.cc new file mode 100644 index 00000000..00f900b3 --- /dev/null +++ b/common/kernel/command.cc @@ -0,0 +1,563 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * 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 NO_GUI +#include +#include "application.h" +#include "mainwindow.h" +#endif +#ifndef NO_PYTHON +#include "pybindings.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "command.h" +#include "design_utils.h" +#include "json_frontend.h" +#include "jsonwrite.h" +#include "log.h" +#include "timing.h" +#include "util.h" +#include "version.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct no_separator : std::numpunct +{ + protected: + virtual string_type do_grouping() const { return "\000"; } // groups of 0 (disable) +}; + +CommandHandler::CommandHandler(int argc, char **argv) : argc(argc), argv(argv) +{ + try { + std::locale loc(""); + std::locale::global(std::locale(loc, new no_separator())); + } catch (const std::runtime_error &e) { + // the locale is broken in this system, so leave it as it is + } + log_streams.clear(); +} + +bool CommandHandler::parseOptions() +{ + options.add(getGeneralOptions()).add(getArchOptions()); + try { + po::parsed_options parsed = + po::command_line_parser(argc, argv) + .style(po::command_line_style::default_style ^ po::command_line_style::allow_guessing) + .options(options) + .positional(pos) + .run(); + po::store(parsed, vm); + po::notify(vm); + return true; + } catch (std::exception &e) { + std::cout << e.what() << "\n"; + return false; + } +} + +bool CommandHandler::executeBeforeContext() +{ + if (vm.count("help") || argc == 1) { + std::cerr << boost::filesystem::basename(argv[0]) + << " -- Next Generation Place and Route (Version " GIT_DESCRIBE_STR ")\n"; + std::cerr << options << "\n"; + return argc != 1; + } + + if (vm.count("version")) { + std::cerr << boost::filesystem::basename(argv[0]) + << " -- Next Generation Place and Route (Version " GIT_DESCRIBE_STR ")\n"; + return true; + } + validate(); + + if (vm.count("quiet")) { + log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING_MSG)); + } else { + log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG_MSG)); + } + + if (vm.count("log")) { + std::string logfilename = vm["log"].as(); + logfile.open(logfilename); + if (!logfile.is_open()) + log_error("Failed to open log file '%s' for writing.\n", logfilename.c_str()); + log_streams.push_back(std::make_pair(&logfile, LogLevel::LOG_MSG)); + } + return false; +} + +po::options_description CommandHandler::getGeneralOptions() +{ + po::options_description general("General options"); + general.add_options()("help,h", "show help"); + general.add_options()("verbose,v", "verbose output"); + general.add_options()("quiet,q", "quiet mode, only errors and warnings displayed"); + general.add_options()("log,l", po::value(), + "log file, all log messages are written to this file regardless of -q"); + general.add_options()("debug", "debug output"); + general.add_options()("debug-placer", "debug output from placer only"); + general.add_options()("debug-router", "debug output from router only"); + general.add_options()("threads", po::value(), "number of threads for passes where this is configurable"); + + general.add_options()("force,f", "keep running after errors"); +#ifndef NO_GUI + general.add_options()("gui", "start gui"); + general.add_options()("gui-no-aa", "disable anti aliasing (use together with --gui option)"); +#endif +#ifndef NO_PYTHON + general.add_options()("run", po::value>(), + "python file to execute instead of default flow"); + pos.add("run", -1); + general.add_options()("pre-pack", po::value>(), "python file to run before packing"); + general.add_options()("pre-place", po::value>(), "python file to run before placement"); + general.add_options()("pre-route", po::value>(), "python file to run before routing"); + general.add_options()("post-route", po::value>(), "python file to run after routing"); + general.add_options()("on-failure", po::value>(), + "python file to run in event of crash for design introspection"); + +#endif + general.add_options()("json", po::value(), "JSON design file to ingest"); + general.add_options()("write", po::value(), "JSON design file to write"); + general.add_options()("top", po::value(), "name of top module"); + general.add_options()("seed", po::value(), "seed value for random number generator"); + general.add_options()("randomize-seed,r", "randomize seed value for random number generator"); + + general.add_options()( + "placer", po::value(), + std::string("placer algorithm to use; available: " + boost::algorithm::join(Arch::availablePlacers, ", ") + + "; default: " + Arch::defaultPlacer) + .c_str()); + + general.add_options()( + "router", po::value(), + std::string("router algorithm to use; available: " + boost::algorithm::join(Arch::availableRouters, ", ") + + "; default: " + Arch::defaultRouter) + .c_str()); + + general.add_options()("slack_redist_iter", po::value(), "number of iterations between slack redistribution"); + general.add_options()("cstrweight", po::value(), "placer weighting for relative constraint satisfaction"); + general.add_options()("starttemp", po::value(), "placer SA start temperature"); + general.add_options()("placer-budgets", "use budget rather than criticality in placer timing weights"); + + general.add_options()("pack-only", "pack design only without placement or routing"); + general.add_options()("no-route", "process design without routing"); + general.add_options()("no-place", "process design without placement"); + general.add_options()("no-pack", "process design without packing"); + + general.add_options()("ignore-loops", "ignore combinational loops in timing analysis"); + + general.add_options()("version,V", "show version"); + general.add_options()("test", "check architecture database integrity"); + general.add_options()("freq", po::value(), "set target frequency for design in MHz"); + general.add_options()("timing-allow-fail", "allow timing to fail in design"); + general.add_options()("no-tmdriv", "disable timing-driven placement"); + general.add_options()("sdf", po::value(), "SDF delay back-annotation file to write"); + general.add_options()("sdf-cvc", "enable tweaks for SDF file compatibility with the CVC simulator"); + general.add_options()("no-print-critical-path-source", + "disable printing of the line numbers associated with each net in the critical path"); + + general.add_options()("placer-heap-alpha", po::value(), "placer heap alpha value (float, default: 0.1)"); + general.add_options()("placer-heap-beta", po::value(), "placer heap beta value (float, default: 0.9)"); + general.add_options()("placer-heap-critexp", po::value(), + "placer heap criticality exponent (int, default: 2)"); + general.add_options()("placer-heap-timingweight", po::value(), "placer heap timing weight (int, default: 10)"); + +#if !defined(__wasm) + general.add_options()("parallel-refine", "use new experimental parallelised engine for placement refinement"); +#endif + + general.add_options()("router2-heatmap", po::value(), + "prefix for router2 resource congestion heatmaps"); + + general.add_options()("tmg-ripup", "enable experimental timing-driven ripup in router"); + general.add_options()("router2-tmg-ripup", + "enable experimental timing-driven ripup in router (deprecated; use --tmg-ripup instead)"); + + general.add_options()("report", po::value(), + "write timing and utilization report in JSON format to file"); + general.add_options()("detailed-timing-report", "Append detailed net timing data to the JSON report"); + + general.add_options()("placed-svg", po::value(), "write render of placement to SVG file"); + general.add_options()("routed-svg", po::value(), "write render of routing to SVG file"); + + return general; +} + +namespace { +static CommandHandler *global_command_handler = nullptr; +void script_terminate_handler() +{ + if (global_command_handler != nullptr) + global_command_handler->run_script_hook("on-failure"); +} +}; // namespace + +void CommandHandler::setupContext(Context *ctx) +{ + if (ctx->settings.find(ctx->id("seed")) != ctx->settings.end()) + ctx->rngstate = ctx->setting("seed"); + + if (vm.count("verbose")) { + ctx->verbose = true; + } + + if (vm.count("debug")) { + ctx->verbose = true; + ctx->debug = true; + } + + if (vm.count("no-print-critical-path-source")) { + ctx->disable_critical_path_source_print = true; + } + + if (vm.count("force")) { + ctx->force = true; + } + + if (vm.count("seed")) { + ctx->rngseed(vm["seed"].as()); + } + + if (vm.count("threads")) { + ctx->settings[ctx->id("threads")] = vm["threads"].as(); + } + + if (vm.count("randomize-seed")) { + std::random_device randDev{}; + std::uniform_int_distribution distrib{1}; + ctx->rngseed(distrib(randDev)); + } + + if (vm.count("slack_redist_iter")) { + ctx->settings[ctx->id("slack_redist_iter")] = vm["slack_redist_iter"].as(); + if (vm.count("freq") && vm["freq"].as() == 0) { + ctx->settings[ctx->id("auto_freq")] = true; +#ifndef NO_GUI + if (!vm.count("gui")) +#endif + log_warning("Target frequency not specified. Will optimise for max frequency.\n"); + } + } + + if (vm.count("ignore-loops")) { + ctx->settings[ctx->id("timing/ignoreLoops")] = true; + } + + if (vm.count("timing-allow-fail")) { + ctx->settings[ctx->id("timing/allowFail")] = true; + } + + if (vm.count("placer")) { + std::string placer = vm["placer"].as(); + if (std::find(Arch::availablePlacers.begin(), Arch::availablePlacers.end(), placer) == + Arch::availablePlacers.end()) + log_error("Placer algorithm '%s' is not supported (available options: %s)\n", placer.c_str(), + boost::algorithm::join(Arch::availablePlacers, ", ").c_str()); + ctx->settings[ctx->id("placer")] = placer; + } + + if (vm.count("router")) { + std::string router = vm["router"].as(); + if (std::find(Arch::availableRouters.begin(), Arch::availableRouters.end(), router) == + Arch::availableRouters.end()) + log_error("Router algorithm '%s' is not supported (available options: %s)\n", router.c_str(), + boost::algorithm::join(Arch::availableRouters, ", ").c_str()); + ctx->settings[ctx->id("router")] = router; + } + + if (vm.count("cstrweight")) { + ctx->settings[ctx->id("placer1/constraintWeight")] = std::to_string(vm["cstrweight"].as()); + } + if (vm.count("starttemp")) { + ctx->settings[ctx->id("placer1/startTemp")] = std::to_string(vm["starttemp"].as()); + } + + if (vm.count("placer-budgets")) { + ctx->settings[ctx->id("placer1/budgetBased")] = true; + } + if (vm.count("freq")) { + auto freq = vm["freq"].as(); + if (freq > 0) + ctx->settings[ctx->id("target_freq")] = std::to_string(freq * 1e6); + } + + if (vm.count("no-tmdriv")) + ctx->settings[ctx->id("timing_driven")] = false; + + if (vm.count("placer-heap-alpha")) + ctx->settings[ctx->id("placerHeap/alpha")] = std::to_string(vm["placer-heap-alpha"].as()); + + if (vm.count("placer-heap-beta")) + ctx->settings[ctx->id("placerHeap/beta")] = std::to_string(vm["placer-heap-beta"].as()); + + if (vm.count("placer-heap-critexp")) + ctx->settings[ctx->id("placerHeap/criticalityExponent")] = std::to_string(vm["placer-heap-critexp"].as()); + + if (vm.count("placer-heap-timingweight")) + ctx->settings[ctx->id("placerHeap/timingWeight")] = std::to_string(vm["placer-heap-timingweight"].as()); + + if (vm.count("parallel-refine")) + ctx->settings[ctx->id("placerHeap/parallelRefine")] = true; + + if (vm.count("router2-heatmap")) + ctx->settings[ctx->id("router2/heatmap")] = vm["router2-heatmap"].as(); + if (vm.count("tmg-ripup") || vm.count("router2-tmg-ripup")) + ctx->settings[ctx->id("router/tmg_ripup")] = true; + + // Setting default values + if (ctx->settings.find(ctx->id("target_freq")) == ctx->settings.end()) + ctx->settings[ctx->id("target_freq")] = std::to_string(12e6); + if (ctx->settings.find(ctx->id("timing_driven")) == ctx->settings.end()) + ctx->settings[ctx->id("timing_driven")] = true; + if (ctx->settings.find(ctx->id("slack_redist_iter")) == ctx->settings.end()) + ctx->settings[ctx->id("slack_redist_iter")] = 0; + if (ctx->settings.find(ctx->id("auto_freq")) == ctx->settings.end()) + ctx->settings[ctx->id("auto_freq")] = false; + if (ctx->settings.find(ctx->id("placer")) == ctx->settings.end()) + ctx->settings[ctx->id("placer")] = Arch::defaultPlacer; + if (ctx->settings.find(ctx->id("router")) == ctx->settings.end()) + ctx->settings[ctx->id("router")] = Arch::defaultRouter; + + ctx->settings[ctx->id("arch.name")] = std::string(ctx->archId().c_str(ctx)); + ctx->settings[ctx->id("arch.type")] = std::string(ctx->archArgsToId(ctx->archArgs()).c_str(ctx)); + ctx->settings[ctx->id("seed")] = ctx->rngstate; + + if (ctx->settings.find(ctx->id("placerHeap/alpha")) == ctx->settings.end()) + ctx->settings[ctx->id("placerHeap/alpha")] = std::to_string(0.1); + if (ctx->settings.find(ctx->id("placerHeap/beta")) == ctx->settings.end()) + ctx->settings[ctx->id("placerHeap/beta")] = std::to_string(0.9); + if (ctx->settings.find(ctx->id("placerHeap/criticalityExponent")) == ctx->settings.end()) + ctx->settings[ctx->id("placerHeap/criticalityExponent")] = std::to_string(2); + if (ctx->settings.find(ctx->id("placerHeap/timingWeight")) == ctx->settings.end()) + ctx->settings[ctx->id("placerHeap/timingWeight")] = std::to_string(10); + + if (vm.count("detailed-timing-report")) { + ctx->detailed_timing_report = true; + } +} + +int CommandHandler::executeMain(std::unique_ptr ctx) +{ + if (vm.count("on-failure")) { + global_command_handler = this; + std::set_terminate(script_terminate_handler); + } + if (vm.count("test")) { + ctx->archcheck(); + return 0; + } + + if (vm.count("top")) { + ctx->settings[ctx->id("frontend/top")] = vm["top"].as(); + } + +#ifndef NO_GUI + if (vm.count("gui")) { + Application a(argc, argv, (vm.count("gui-no-aa") > 0)); + MainWindow w(std::move(ctx), this); + try { + if (vm.count("json")) { + std::string filename = vm["json"].as(); + std::ifstream f(filename); + if (!parse_json(f, filename, w.getContext())) + log_error("Loading design failed.\n"); + customAfterLoad(w.getContext()); + w.notifyChangeContext(); + w.updateActions(); + } else + w.notifyChangeContext(); + } catch (log_execution_error_exception) { + // show error is handled by gui itself + } + w.show(); + + return a.exec(); + } +#endif + if (vm.count("json")) { + std::string filename = vm["json"].as(); + std::ifstream f(filename); + if (!parse_json(f, filename, ctx.get())) + log_error("Loading design failed.\n"); + + customAfterLoad(ctx.get()); + } + +#ifndef NO_PYTHON + init_python(argv[0]); + python_export_global("ctx", *ctx); + + if (vm.count("run")) { + + std::vector files = vm["run"].as>(); + for (auto filename : files) + execute_python_file(filename.c_str()); + } else +#endif + if (ctx->design_loaded) { + bool do_pack = vm.count("pack-only") != 0 || vm.count("no-pack") == 0; + bool do_place = vm.count("pack-only") == 0 && vm.count("no-place") == 0; + bool do_route = vm.count("pack-only") == 0 && vm.count("no-route") == 0; + + if (do_pack) { + run_script_hook("pre-pack"); + if (!ctx->pack() && !ctx->force) + log_error("Packing design failed.\n"); + } + assign_budget(ctx.get()); + ctx->check(); + print_utilisation(ctx.get()); + + if (do_place) { + run_script_hook("pre-place"); + bool saved_debug = ctx->debug; + if (vm.count("debug-placer")) + ctx->debug = true; + if (!ctx->place() && !ctx->force) + log_error("Placing design failed.\n"); + ctx->debug = saved_debug; + ctx->check(); + if (vm.count("placed-svg")) + ctx->writeSVG(vm["placed-svg"].as(), "scale=50 hide_routing"); + } + + if (do_route) { + run_script_hook("pre-route"); + bool saved_debug = ctx->debug; + if (vm.count("debug-router")) + ctx->debug = true; + if (!ctx->route() && !ctx->force) + log_error("Routing design failed.\n"); + ctx->debug = saved_debug; + run_script_hook("post-route"); + if (vm.count("routed-svg")) + ctx->writeSVG(vm["routed-svg"].as(), "scale=500"); + } + + customBitstream(ctx.get()); + } + + if (vm.count("write")) { + std::string filename = vm["write"].as(); + std::ofstream f(filename); + if (!write_json_file(f, filename, ctx.get())) + log_error("Saving design failed.\n"); + } + + if (vm.count("sdf")) { + std::string filename = vm["sdf"].as(); + std::ofstream f(filename); + if (!f) + log_error("Failed to open SDF file '%s' for writing.\n", filename.c_str()); + ctx->writeSDF(f, vm.count("sdf-cvc")); + } + + if (vm.count("report")) { + std::string filename = vm["report"].as(); + std::ofstream f(filename); + if (!f) + log_error("Failed to open report file '%s' for writing.\n", filename.c_str()); + ctx->writeReport(f); + } + +#ifndef NO_PYTHON + deinit_python(); +#endif + + return had_nonfatal_error ? 1 : 0; +} + +void CommandHandler::conflicting_options(const boost::program_options::variables_map &vm, const char *opt1, + const char *opt2) +{ + if (vm.count(opt1) && !vm[opt1].defaulted() && vm.count(opt2) && !vm[opt2].defaulted()) { + std::string msg = "Conflicting options '" + std::string(opt1) + "' and '" + std::string(opt2) + "'."; + log_error("%s\n", msg.c_str()); + } +} + +void CommandHandler::printFooter() +{ + int warning_count = get_or_default(message_count_by_level, LogLevel::WARNING_MSG, 0), + error_count = get_or_default(message_count_by_level, LogLevel::ERROR_MSG, 0); + if (warning_count > 0 || error_count > 0) + log_always("%d warning%s, %d error%s\n", warning_count, warning_count == 1 ? "" : "s", error_count, + error_count == 1 ? "" : "s"); +} + +int CommandHandler::exec() +{ + try { + if (!parseOptions()) + return -1; + + if (executeBeforeContext()) + return 0; + + dict values; + std::unique_ptr ctx = createContext(values); + setupContext(ctx.get()); + setupArchContext(ctx.get()); + int rc = executeMain(std::move(ctx)); + printFooter(); + log_break(); + log_info("Program finished normally.\n"); + return rc; + } catch (log_execution_error_exception) { + printFooter(); + return -1; + } +} + +void CommandHandler::load_json(Context *ctx, std::string filename) +{ + setupContext(ctx); + setupArchContext(ctx); + { + std::ifstream f(filename); + if (!parse_json(f, filename, ctx)) + log_error("Loading design failed.\n"); + } +} + +void CommandHandler::clear() { vm.clear(); } + +void CommandHandler::run_script_hook(const std::string &name) +{ +#ifndef NO_PYTHON + if (vm.count(name)) { + std::vector files = vm[name].as>(); + for (auto filename : files) + execute_python_file(filename.c_str()); + } +#endif +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/command.h b/common/kernel/command.h new file mode 100644 index 00000000..6cce8c61 --- /dev/null +++ b/common/kernel/command.h @@ -0,0 +1,74 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * 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 COMMAND_H +#define COMMAND_H + +#include +#include +#include "log.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace po = boost::program_options; + +class CommandHandler +{ + public: + CommandHandler(int argc, char **argv); + virtual ~CommandHandler(){}; + + int exec(); + void load_json(Context *ctx, std::string filename); + void clear(); + void run_script_hook(const std::string &name); + + protected: + virtual void setupArchContext(Context *ctx) = 0; + virtual std::unique_ptr createContext(dict &values) = 0; + virtual po::options_description getArchOptions() = 0; + virtual void validate(){}; + virtual void customAfterLoad(Context *ctx){}; + virtual void customBitstream(Context *ctx){}; + void conflicting_options(const boost::program_options::variables_map &vm, const char *opt1, const char *opt2); + + private: + bool parseOptions(); + bool executeBeforeContext(); + void setupContext(Context *ctx); + int executeMain(std::unique_ptr ctx); + po::options_description getGeneralOptions(); + void printFooter(); + + protected: + po::variables_map vm; + + private: + po::options_description options; + po::positional_options_description pos; + int argc; + char **argv; + std::ofstream logfile; +}; + +NEXTPNR_NAMESPACE_END + +#endif // COMMAND_H diff --git a/common/kernel/constraints.h b/common/kernel/constraints.h new file mode 100644 index 00000000..65abf12c --- /dev/null +++ b/common/kernel/constraints.h @@ -0,0 +1,70 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 The SymbiFlow Authors. + * + * 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 CONSTRAINTS_H +#define CONSTRAINTS_H + +#include +#include + +#include "archdefs.h" +#include "exclusive_state_groups.h" +#include "hashlib.h" +#include "idstring.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct Context; + +template struct Constraints +{ + using ConstraintStateType = StateType; + using ConstraintCountType = CountType; + + enum ConstraintType + { + CONSTRAINT_TAG_IMPLIES = 0, + CONSTRAINT_TAG_REQUIRES = 1, + }; + + template struct Constraint + { + virtual std::size_t tag() const = 0; + virtual ConstraintType constraint_type() const = 0; + virtual StateType state() const = 0; + virtual StateRange states() const = 0; + }; + + typedef ExclusiveStateGroup TagState; + dict> definitions; + + template void bindBel(TagState *tags, const ConstraintRange constraints); + + template void unbindBel(TagState *tags, const ConstraintRange constraints); + + template + bool isValidBelForCellType(const Context *ctx, uint32_t prototype, const TagState *tags, + const ConstraintRange constraints, IdString object, IdString cell, BelId bel, + bool explain_constraints) const; +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/kernel/constraints.impl.h b/common/kernel/constraints.impl.h new file mode 100644 index 00000000..9c978411 --- /dev/null +++ b/common/kernel/constraints.impl.h @@ -0,0 +1,109 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 The SymbiFlow Authors. + * + * 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 CONSTRAINTS_IMPL_H +#define CONSTRAINTS_IMPL_H + +#include "exclusive_state_groups.impl.h" + +NEXTPNR_NAMESPACE_BEGIN + +template +template +void Constraints::bindBel(TagState *tags, const ConstraintRange constraints) +{ + for (const auto &constraint : constraints) { + switch (constraint.constraint_type()) { + case CONSTRAINT_TAG_IMPLIES: + tags[constraint.tag()].add_implies(constraint.state()); + break; + case CONSTRAINT_TAG_REQUIRES: + break; + default: + NPNR_ASSERT(false); + } + } +} + +template +template +void Constraints::unbindBel(TagState *tags, const ConstraintRange constraints) +{ + for (const auto &constraint : constraints) { + switch (constraint.constraint_type()) { + case CONSTRAINT_TAG_IMPLIES: + tags[constraint.tag()].remove_implies(constraint.state()); + break; + case CONSTRAINT_TAG_REQUIRES: + break; + default: + NPNR_ASSERT(false); + } + } +} + +template +template +bool Constraints::isValidBelForCellType(const Context *ctx, uint32_t prototype, + const TagState *tags, + const ConstraintRange constraints, + IdString object, IdString cell, BelId bel, + bool explain_constraints) const +{ + if (explain_constraints) { + auto &state_definition = definitions.at(prototype); + for (const auto &constraint : constraints) { + switch (constraint.constraint_type()) { + case CONSTRAINT_TAG_IMPLIES: + tags[constraint.tag()].explain_implies(ctx, object, cell, state_definition.at(constraint.tag()), bel, + constraint.state()); + break; + case CONSTRAINT_TAG_REQUIRES: + tags[constraint.tag()].explain_requires(ctx, object, cell, state_definition.at(constraint.tag()), bel, + constraint.states()); + break; + default: + NPNR_ASSERT(false); + } + } + } + + for (const auto &constraint : constraints) { + switch (constraint.constraint_type()) { + case CONSTRAINT_TAG_IMPLIES: + if (!tags[constraint.tag()].check_implies(constraint.state())) { + return false; + } + break; + case CONSTRAINT_TAG_REQUIRES: + if (!tags[constraint.tag()].requires(constraint.states())) { + return false; + } + break; + default: + NPNR_ASSERT(false); + } + } + + return true; +} + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/kernel/context.cc b/common/kernel/context.cc new file mode 100644 index 00000000..e35d3e49 --- /dev/null +++ b/common/kernel/context.cc @@ -0,0 +1,428 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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 "context.h" + +#include "log.h" +#include "nextpnr_namespaces.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +WireId Context::getNetinfoSourceWire(const NetInfo *net_info) const +{ + if (net_info->driver.cell == nullptr) + return WireId(); + + auto src_bel = net_info->driver.cell->bel; + + if (src_bel == BelId()) + return WireId(); + + auto bel_pins = getBelPinsForCellPin(net_info->driver.cell, net_info->driver.port); + auto iter = bel_pins.begin(); + if (iter == bel_pins.end()) + return WireId(); + WireId driver = getBelPinWire(src_bel, *iter); + ++iter; + NPNR_ASSERT(iter == bel_pins.end()); // assert there is only one driver bel pin; + return driver; +} + +SSOArray Context::getNetinfoSinkWires(const NetInfo *net_info, const PortRef &user_info) const +{ + auto dst_bel = user_info.cell->bel; + if (dst_bel == BelId()) + return SSOArray(0, WireId()); + size_t bel_pin_count = 0; + // We use an SSOArray here because it avoids any heap allocation for the 99.9% case of 1 or 2 sink wires + // but as SSOArray doesn't (currently) support resizing to keep things simple it does mean we have to do + // two loops + for (auto s : getBelPinsForCellPin(user_info.cell, user_info.port)) { + (void)s; // unused + ++bel_pin_count; + } + SSOArray result(bel_pin_count, WireId()); + bel_pin_count = 0; + for (auto pin : getBelPinsForCellPin(user_info.cell, user_info.port)) { + result[bel_pin_count++] = getBelPinWire(dst_bel, pin); + } + return result; +} + +size_t Context::getNetinfoSinkWireCount(const NetInfo *net_info, const PortRef &sink) const +{ + size_t count = 0; + for (auto s : getNetinfoSinkWires(net_info, sink)) { + (void)s; // unused + ++count; + } + return count; +} + +WireId Context::getNetinfoSinkWire(const NetInfo *net_info, const PortRef &sink, size_t phys_idx) const +{ + size_t count = 0; + for (auto s : getNetinfoSinkWires(net_info, sink)) { + if (count == phys_idx) + return s; + ++count; + } + /* TODO: This should be an assertion failure, but for the zero-wire case of unplaced sinks; legacy code currently + assumes WireId Remove once the refactoring process is complete. + */ + return WireId(); +} + +delay_t Context::predictArcDelay(const NetInfo *net_info, const PortRef &sink) const +{ + if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId() || sink.cell->bel == BelId()) + return 0; + IdString driver_pin, sink_pin; + // Pick the first pin for a prediction; assume all will be similar enouhg + for (auto pin : getBelPinsForCellPin(net_info->driver.cell, net_info->driver.port)) { + driver_pin = pin; + break; + } + for (auto pin : getBelPinsForCellPin(sink.cell, sink.port)) { + sink_pin = pin; + break; + } + if (driver_pin == IdString() || sink_pin == IdString()) + return 0; + return predictDelay(net_info->driver.cell->bel, driver_pin, sink.cell->bel, sink_pin); +} + +delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &user_info) const +{ +#ifdef ARCH_ECP5 + if (net_info->is_global) + return 0; +#endif + + if (net_info->wires.empty()) + return predictArcDelay(net_info, user_info); + + WireId src_wire = getNetinfoSourceWire(net_info); + if (src_wire == WireId()) + return 0; + + delay_t max_delay = 0; + + for (auto dst_wire : getNetinfoSinkWires(net_info, user_info)) { + WireId cursor = dst_wire; + delay_t delay = 0; + + while (cursor != WireId() && cursor != src_wire) { + auto it = net_info->wires.find(cursor); + + if (it == net_info->wires.end()) + break; + + PipId pip = it->second.pip; + if (pip == PipId()) + break; + + delay += getPipDelay(pip).maxDelay(); + delay += getWireDelay(cursor).maxDelay(); + cursor = getPipSrcWire(pip); + } + + if (cursor == src_wire) + max_delay = std::max(max_delay, delay + getWireDelay(src_wire).maxDelay()); // routed + else + max_delay = std::max(max_delay, predictArcDelay(net_info, user_info)); // unrouted + } + return max_delay; +} + +static uint32_t xorshift32(uint32_t x) +{ + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + return x; +} + +uint32_t Context::checksum() const +{ + uint32_t cksum = xorshift32(123456789); + + uint32_t cksum_nets_sum = 0; + for (auto &it : nets) { + auto &ni = *it.second; + uint32_t x = 123456789; + x = xorshift32(x + xorshift32(it.first.index)); + x = xorshift32(x + xorshift32(ni.name.index)); + if (ni.driver.cell) + x = xorshift32(x + xorshift32(ni.driver.cell->name.index)); + x = xorshift32(x + xorshift32(ni.driver.port.index)); + x = xorshift32(x + xorshift32(getDelayChecksum(ni.driver.budget))); + + for (auto &u : ni.users) { + if (u.cell) + x = xorshift32(x + xorshift32(u.cell->name.index)); + x = xorshift32(x + xorshift32(u.port.index)); + x = xorshift32(x + xorshift32(getDelayChecksum(u.budget))); + } + + uint32_t attr_x_sum = 0; + for (auto &a : ni.attrs) { + uint32_t attr_x = 123456789; + attr_x = xorshift32(attr_x + xorshift32(a.first.index)); + for (char ch : a.second.str) + attr_x = xorshift32(attr_x + xorshift32((int)ch)); + attr_x_sum += attr_x; + } + x = xorshift32(x + xorshift32(attr_x_sum)); + + uint32_t wire_x_sum = 0; + for (auto &w : ni.wires) { + uint32_t wire_x = 123456789; + wire_x = xorshift32(wire_x + xorshift32(getWireChecksum(w.first))); + wire_x = xorshift32(wire_x + xorshift32(getPipChecksum(w.second.pip))); + wire_x = xorshift32(wire_x + xorshift32(int(w.second.strength))); + wire_x_sum += wire_x; + } + x = xorshift32(x + xorshift32(wire_x_sum)); + + cksum_nets_sum += x; + } + cksum = xorshift32(cksum + xorshift32(cksum_nets_sum)); + + uint32_t cksum_cells_sum = 0; + for (auto &it : cells) { + auto &ci = *it.second; + uint32_t x = 123456789; + x = xorshift32(x + xorshift32(it.first.index)); + x = xorshift32(x + xorshift32(ci.name.index)); + x = xorshift32(x + xorshift32(ci.type.index)); + + uint32_t port_x_sum = 0; + for (auto &p : ci.ports) { + uint32_t port_x = 123456789; + port_x = xorshift32(port_x + xorshift32(p.first.index)); + port_x = xorshift32(port_x + xorshift32(p.second.name.index)); + if (p.second.net) + port_x = xorshift32(port_x + xorshift32(p.second.net->name.index)); + port_x = xorshift32(port_x + xorshift32(p.second.type)); + port_x_sum += port_x; + } + x = xorshift32(x + xorshift32(port_x_sum)); + + uint32_t attr_x_sum = 0; + for (auto &a : ci.attrs) { + uint32_t attr_x = 123456789; + attr_x = xorshift32(attr_x + xorshift32(a.first.index)); + for (char ch : a.second.str) + attr_x = xorshift32(attr_x + xorshift32((int)ch)); + attr_x_sum += attr_x; + } + x = xorshift32(x + xorshift32(attr_x_sum)); + + uint32_t param_x_sum = 0; + for (auto &p : ci.params) { + uint32_t param_x = 123456789; + param_x = xorshift32(param_x + xorshift32(p.first.index)); + for (char ch : p.second.str) + param_x = xorshift32(param_x + xorshift32((int)ch)); + param_x_sum += param_x; + } + x = xorshift32(x + xorshift32(param_x_sum)); + + x = xorshift32(x + xorshift32(getBelChecksum(ci.bel))); + x = xorshift32(x + xorshift32(ci.belStrength)); + + cksum_cells_sum += x; + } + cksum = xorshift32(cksum + xorshift32(cksum_cells_sum)); + + return cksum; +} + +void Context::check() const +{ + bool check_failed = false; + +#define CHECK_FAIL(...) \ + do { \ + log_nonfatal_error(__VA_ARGS__); \ + check_failed = true; \ + } while (false) + + for (auto &n : nets) { + auto ni = n.second.get(); + if (n.first != ni->name) + CHECK_FAIL("net key '%s' not equal to name '%s'\n", nameOf(n.first), nameOf(ni->name)); + for (auto &w : ni->wires) { + if (ni != getBoundWireNet(w.first)) + CHECK_FAIL("net '%s' not bound to wire '%s' in wires map\n", nameOf(n.first), nameOfWire(w.first)); + if (w.second.pip != PipId()) { + if (w.first != getPipDstWire(w.second.pip)) + CHECK_FAIL("net '%s' has dest mismatch '%s' vs '%s' in for pip '%s'\n", nameOf(n.first), + nameOfWire(w.first), nameOfWire(getPipDstWire(w.second.pip)), nameOfPip(w.second.pip)); + if (ni != getBoundPipNet(w.second.pip)) + CHECK_FAIL("net '%s' not bound to pip '%s' in wires map\n", nameOf(n.first), + nameOfPip(w.second.pip)); + } + } + if (ni->driver.cell != nullptr) { + if (!ni->driver.cell->ports.count(ni->driver.port)) { + CHECK_FAIL("net '%s' driver port '%s' missing on cell '%s'\n", nameOf(n.first), nameOf(ni->driver.port), + nameOf(ni->driver.cell)); + } else { + const NetInfo *p_net = ni->driver.cell->ports.at(ni->driver.port).net; + if (p_net != ni) + CHECK_FAIL("net '%s' driver port '%s.%s' connected to incorrect net '%s'\n", nameOf(n.first), + nameOf(ni->driver.cell), nameOf(ni->driver.port), p_net ? nameOf(p_net) : ""); + } + } + for (auto user : ni->users) { + if (!user.cell->ports.count(user.port)) { + CHECK_FAIL("net '%s' user port '%s' missing on cell '%s'\n", nameOf(n.first), nameOf(user.port), + nameOf(user.cell)); + } else { + const NetInfo *p_net = user.cell->ports.at(user.port).net; + if (p_net != ni) + CHECK_FAIL("net '%s' user port '%s.%s' connected to incorrect net '%s'\n", nameOf(n.first), + nameOf(user.cell), nameOf(user.port), p_net ? nameOf(p_net) : ""); + } + } + } +#ifdef CHECK_WIRES + for (auto w : getWires()) { + auto ni = getBoundWireNet(w); + if (ni != nullptr) { + if (!ni->wires.count(w)) + CHECK_FAIL("wire '%s' missing in wires map of bound net '%s'\n", nameOfWire(w), nameOf(ni)); + } + } +#endif + for (auto &c : cells) { + auto ci = c.second.get(); + if (c.first != ci->name) + CHECK_FAIL("cell key '%s' not equal to name '%s'\n", nameOf(c.first), nameOf(ci->name)); + if (ci->bel != BelId()) { + if (getBoundBelCell(c.second->bel) != ci) + CHECK_FAIL("cell '%s' not bound to bel '%s' in bel field\n", nameOf(c.first), nameOfBel(ci->bel)); + } + for (auto &port : c.second->ports) { + NetInfo *net = port.second.net; + if (net != nullptr) { + if (nets.find(net->name) == nets.end()) { + CHECK_FAIL("cell port '%s.%s' connected to non-existent net '%s'\n", nameOf(c.first), + nameOf(port.first), nameOf(net->name)); + } else if (port.second.type == PORT_OUT) { + if (net->driver.cell != c.second.get() || net->driver.port != port.first) { + CHECK_FAIL("output cell port '%s.%s' not in driver field of net '%s'\n", nameOf(c.first), + nameOf(port.first), nameOf(net)); + } + } else if (port.second.type == PORT_IN) { + if (!port.second.user_idx) + CHECK_FAIL("input cell port '%s.%s' on net '%s' has no user index\n", nameOf(c.first), + nameOf(port.first), nameOf(net)); + auto net_user = net->users.at(port.second.user_idx); + if (net_user.cell != c.second.get() || net_user.port != port.first) + CHECK_FAIL("input cell port '%s.%s' not in associated user entry of net '%s'\n", + nameOf(c.first), nameOf(port.first), nameOf(net)); + } + } + } + } + +#undef CHECK_FAIL + + if (check_failed) + log_error("INTERNAL CHECK FAILED: please report this error with the design and full log output. Failure " + "details are above this message.\n"); +} + +namespace { +struct FixupHierarchyWorker +{ + FixupHierarchyWorker(Context *ctx) : ctx(ctx){}; + Context *ctx; + void run() + { + trim_hierarchy(ctx->top_module); + rebuild_hierarchy(); + }; + // Remove cells and nets that no longer exist in the netlist + std::vector todelete_cells, todelete_nets; + void trim_hierarchy(IdString path) + { + auto &h = ctx->hierarchy.at(path); + todelete_cells.clear(); + todelete_nets.clear(); + for (auto &lc : h.leaf_cells) { + if (!ctx->cells.count(lc.second)) + todelete_cells.push_back(lc.first); + } + for (auto &n : h.nets) + if (!ctx->nets.count(n.second)) + todelete_nets.push_back(n.first); + for (auto tdc : todelete_cells) { + h.leaf_cells_by_gname.erase(h.leaf_cells.at(tdc)); + h.leaf_cells.erase(tdc); + } + for (auto tdn : todelete_nets) { + h.nets_by_gname.erase(h.nets.at(tdn)); + h.nets.erase(tdn); + } + for (auto &sc : h.hier_cells) + trim_hierarchy(sc.second); + } + + IdString construct_local_name(HierarchicalCell &hc, IdString global_name, bool is_cell) + { + std::string gn = global_name.str(ctx); + auto dp = gn.find_last_of('.'); + if (dp != std::string::npos) + gn = gn.substr(dp + 1); + IdString name = ctx->id(gn); + // Make sure name is unique + int adder = 0; + while (is_cell ? hc.leaf_cells.count(name) : hc.nets.count(name)) { + ++adder; + name = ctx->id(gn + "$" + std::to_string(adder)); + } + return name; + } + + // Update hierarchy structure for nets and cells that have hiercell set + void rebuild_hierarchy() + { + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->hierpath == IdString()) + ci->hierpath = ctx->top_module; + auto &hc = ctx->hierarchy.at(ci->hierpath); + if (hc.leaf_cells_by_gname.count(ci->name)) + continue; // already known + IdString local_name = construct_local_name(hc, ci->name, true); + hc.leaf_cells_by_gname[ci->name] = local_name; + hc.leaf_cells[local_name] = ci->name; + } + } +}; +} // namespace + +void Context::fixupHierarchy() { FixupHierarchyWorker(this).run(); } + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/context.h b/common/kernel/context.h new file mode 100644 index 00000000..cb8fd257 --- /dev/null +++ b/common/kernel/context.h @@ -0,0 +1,119 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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 CONTEXT_H +#define CONTEXT_H + +#include + +#include "arch.h" +#include "deterministic_rng.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct Context : Arch, DeterministicRNG +{ + bool verbose = false; + bool debug = false; + bool force = false; + + // Should we disable printing of the location of nets in the critical path? + bool disable_critical_path_source_print = false; + // True when detailed per-net timing is to be stored / reported + bool detailed_timing_report = false; + + ArchArgs arch_args; + + Context(ArchArgs args) : Arch(args) + { + BaseCtx::as_ctx = this; + arch_args = args; + } + + ArchArgs getArchArgs() { return arch_args; } + + // -------------------------------------------------------------- + + delay_t predictArcDelay(const NetInfo *net_info, const PortRef &sink) const; + + WireId getNetinfoSourceWire(const NetInfo *net_info) const; + SSOArray getNetinfoSinkWires(const NetInfo *net_info, const PortRef &sink) const; + size_t getNetinfoSinkWireCount(const NetInfo *net_info, const PortRef &sink) const; + WireId getNetinfoSinkWire(const NetInfo *net_info, const PortRef &sink, size_t phys_idx) const; + delay_t getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &sink) const; + + // provided by router1.cc + bool checkRoutedDesign() const; + bool getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t *delay = nullptr, + dict *route = nullptr, bool useEstimate = true); + + // -------------------------------------------------------------- + // call after changing hierpath or adding/removing nets and cells + void fixupHierarchy(); + + // -------------------------------------------------------------- + + // provided by sdf.cc + void writeSDF(std::ostream &out, bool cvc_mode = false) const; + + // -------------------------------------------------------------- + + // provided by svg.cc + void writeSVG(const std::string &filename, const std::string &flags = "") const; + + // -------------------------------------------------------------- + + // provided by report.cc + void writeReport(std::ostream &out) const; + // -------------------------------------------------------------- + + uint32_t checksum() const; + + void check() const; + void archcheck() const; + + template T setting(const char *name, T defaultValue) + { + IdString new_id = id(name); + auto found = settings.find(new_id); + if (found != settings.end()) + return boost::lexical_cast(found->second.is_string ? found->second.as_string() + : std::to_string(found->second.as_int64())); + else + settings[id(name)] = std::to_string(defaultValue); + + return defaultValue; + } + + template T setting(const char *name) const + { + IdString new_id = id(name); + auto found = settings.find(new_id); + if (found != settings.end()) + return boost::lexical_cast(found->second.is_string ? found->second.as_string() + : std::to_string(found->second.as_int64())); + else + throw std::runtime_error("settings does not exists"); + } +}; + +NEXTPNR_NAMESPACE_END + +#endif /* CONTEXT_H */ diff --git a/common/kernel/design_utils.cc b/common/kernel/design_utils.cc new file mode 100644 index 00000000..f52cc304 --- /dev/null +++ b/common/kernel/design_utils.cc @@ -0,0 +1,52 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * 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. + * + */ + +#include "design_utils.h" +#include +#include +#include "log.h" +#include "util.h" +NEXTPNR_NAMESPACE_BEGIN + +// Print utilisation of a design +void print_utilisation(const Context *ctx) +{ + // Sort by Bel type + std::map used_types; + for (auto &cell : ctx->cells) { + used_types[ctx->getBelBucketName(ctx->getBelBucketForCellType(cell.second.get()->type))]++; + } + std::map available_types; + for (auto bel : ctx->getBels()) { + if (!ctx->getBelHidden(bel)) { + available_types[ctx->getBelBucketName(ctx->getBelBucketForBel(bel))]++; + } + } + log_break(); + log_info("Device utilisation:\n"); + for (auto type : available_types) { + IdString type_id = type.first; + int used_bels = get_or_default(used_types, type.first, 0); + log_info("\t%20s: %5d/%5d %5d%%\n", type_id.c_str(ctx), used_bels, type.second, 100 * used_bels / type.second); + } + log_break(); +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/design_utils.h b/common/kernel/design_utils.h new file mode 100644 index 00000000..069600b5 --- /dev/null +++ b/common/kernel/design_utils.h @@ -0,0 +1,100 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * 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. + * + */ + +#include "nextpnr.h" + +#ifndef DESIGN_UTILS_H +#define DESIGN_UTILS_H + +#include + +NEXTPNR_NAMESPACE_BEGIN + +/* +Utilities for design manipulation, intended for use inside packing algorithms + */ + +// Disconnect a net (if connected) from old, and connect it to rep +void replace_port(CellInfo *old_cell, IdString old_name, CellInfo *rep_cell, IdString rep_name); + +// If a net drives a given port of a cell matching a predicate (in many +// cases more than one cell type, e.g. SB_DFFxx so a predicate is used), return +// the first instance of that cell (otherwise nullptr). If exclusive is set to +// true, then this cell must be the only load. If ignore_cell is set, that cell +// is not considered +template +CellInfo *net_only_drives(const Context *ctx, NetInfo *net, F1 cell_pred, IdString port, bool exclusive = false, + CellInfo *exclude = nullptr) +{ + if (net == nullptr) + return nullptr; + if (exclusive) { + if (exclude == nullptr) { + if (net->users.entries() != 1) + return nullptr; + } else { + if (net->users.entries() > 2) { + return nullptr; + } else if (net->users.entries() == 2) { + bool found = false; + for (auto &usr : net->users) { + if (usr.cell == exclude) + found = true; + } + if (!found) + return nullptr; + } + } + } + for (const auto &load : net->users) { + if (load.cell != exclude && cell_pred(ctx, load.cell) && load.port == port) { + return load.cell; + } + } + return nullptr; +} + +// If a net is driven by a given port of a cell matching a predicate, return +// that cell, otherwise nullptr +template CellInfo *net_driven_by(const Context *ctx, const NetInfo *net, F1 cell_pred, IdString port) +{ + if (net == nullptr) + return nullptr; + if (net->driver.cell == nullptr) + return nullptr; + if (cell_pred(ctx, net->driver.cell) && net->driver.port == port) { + return net->driver.cell; + } else { + return nullptr; + } +} + +// Check if a port is used +inline bool port_used(CellInfo *cell, IdString port_name) +{ + auto port_fnd = cell->ports.find(port_name); + return port_fnd != cell->ports.end() && port_fnd->second.net != nullptr; +} + +void print_utilisation(const Context *ctx); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/kernel/deterministic_rng.h b/common/kernel/deterministic_rng.h new file mode 100644 index 00000000..3aab5a49 --- /dev/null +++ b/common/kernel/deterministic_rng.h @@ -0,0 +1,103 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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 DETERMINISTIC_RNG_H +#define DETERMINISTIC_RNG_H + +#include +#include +#include +#include + +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct DeterministicRNG +{ + uint64_t rngstate; + + DeterministicRNG() : rngstate(0x3141592653589793) {} + + uint64_t rng64() + { + // xorshift64star + // https://arxiv.org/abs/1402.6246 + + uint64_t retval = rngstate * 0x2545F4914F6CDD1D; + + rngstate ^= rngstate >> 12; + rngstate ^= rngstate << 25; + rngstate ^= rngstate >> 27; + + return retval; + } + + int rng() { return rng64() & 0x3fffffff; } + + int rng(int n) + { + assert(n > 0); + + // round up to power of 2 + int m = n - 1; + m |= (m >> 1); + m |= (m >> 2); + m |= (m >> 4); + m |= (m >> 8); + m |= (m >> 16); + m += 1; + + while (1) { + int x = rng64() & (m - 1); + if (x < n) + return x; + } + } + + void rngseed(uint64_t seed) + { + rngstate = seed ? seed : 0x3141592653589793; + for (int i = 0; i < 5; i++) + rng64(); + } + + template void shuffle(const Iter &begin, const Iter &end) + { + std::size_t size = end - begin; + for (std::size_t i = 0; i != size; i++) { + std::size_t j = i + rng(size - i); + if (j > i) + std::swap(*(begin + i), *(begin + j)); + } + } + + template void shuffle(std::vector &a) { shuffle(a.begin(), a.end()); } + + template void sorted_shuffle(std::vector &a) + { + std::sort(a.begin(), a.end()); + shuffle(a); + } +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/kernel/dynamic_bitarray.h b/common/kernel/dynamic_bitarray.h new file mode 100644 index 00000000..be41835b --- /dev/null +++ b/common/kernel/dynamic_bitarray.h @@ -0,0 +1,211 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 Symbiflow Authors + * + * 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 DYNAMIC_BITARRAY_H +#define DYNAMIC_BITARRAY_H + +#include +#include +#include + +#include "log.h" +#include "nextpnr_assertions.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +// This class implements a simple dynamic bitarray, backed by some resizable +// random access storage. The default is to use a std::vector. +template > class DynamicBitarray +{ + public: + static_assert(!std::numeric_limits::is_signed, "Storage must be unsigned!"); + + void fill(bool value) + { + std::fill(storage.begin(), storage.end(), value ? std::numeric_limits::max() : 0); + } + + constexpr size_t bits_per_value() const { return std::numeric_limits::digits; } + + bool get(size_t bit) const + { + size_t element_index = bit / bits_per_value(); + size_t bit_offset = bit % bits_per_value(); + + auto element = storage.at(element_index); + return (element & (1 << bit_offset)) != 0; + } + + void set(size_t bit, bool value) + { + size_t element_index = bit / bits_per_value(); + size_t bit_offset = bit % bits_per_value(); + + if (value) { + storage.at(element_index) |= (1 << bit_offset); + } else { + storage.at(element_index) &= ~(1 << bit_offset); + } + } + + void resize(size_t number_bits) + { + size_t required_storage = (number_bits + bits_per_value() - 1) / bits_per_value(); + storage.resize(required_storage); + } + + size_t size() const { return storage.size() * bits_per_value(); } + + void clear() { return storage.clear(); } + + // Convert IntType to a DynamicBitarray of sufficent width + template static DynamicBitarray to_bitarray(const IntType &value) + { + if (std::numeric_limits::is_signed) { + if (value < 0) { + log_error("Expected position value, got %s\n", std::to_string(value).c_str()); + } + } + + DynamicBitarray result; + result.resize(std::numeric_limits::digits); + result.fill(false); + + // Use a 1 of the right type (for shifting) + IntType one(1); + + for (size_t i = 0; i < std::numeric_limits::digits; ++i) { + if ((value & (one << i)) != 0) { + result.set(i, true); + } + } + + return result; + } + + // Convert binary bitstring to a DynamicBitarray of sufficent width + // + // string must be satisfy the following regex: + // + // [01]+ + // + // width can either be specified explicitly, or -1 to use a size wide + // enough to store the given string. + // + // If the width is specified and the width is insufficent it will result + // in an error. + static DynamicBitarray parse_binary_bitstring(int width, const std::string &bits) + { + NPNR_ASSERT(width == -1 || width > 0); + + DynamicBitarray result; + // If no width was supplied, use the width from the input data. + if (width == -1) { + width = bits.size(); + } + + NPNR_ASSERT(width >= 0); + if ((size_t)width < bits.size()) { + log_error("String '%s' is wider than specified width %d\n", bits.c_str(), width); + } + result.resize(width); + result.fill(false); + + for (size_t i = 0; i < bits.size(); ++i) { + // bits[0] is the MSB! + size_t index = width - 1 - i; + if (!(bits[i] == '1' || bits[i] == '0')) { + log_error("String '%s' is not a valid binary bitstring?\n", bits.c_str()); + } + result.set(index, bits[i] == '1'); + } + + return result; + } + + // Convert hex bitstring to a DynamicBitarray of sufficent width + // + // string must be satisfy the following regex: + // + // [0-9a-fA-F]+ + // + // width can either be specified explicitly, or -1 to use a size wide + // enough to store the given string. + // + // If the width is specified and the width is insufficent it will result + // in an error. + static DynamicBitarray parse_hex_bitstring(int width, const std::string &bits) + { + NPNR_ASSERT(width == -1 || width > 0); + + DynamicBitarray result; + // If no width was supplied, use the width from the input data. + if (width == -1) { + // Each character is 4 bits! + width = bits.size() * 4; + } + + NPNR_ASSERT(width >= 0); + int rem = width % 4; + size_t check_width = width; + if (rem != 0) { + check_width += (4 - rem); + } + if (check_width < bits.size() * 4) { + log_error("String '%s' is wider than specified width %d (check_width = %zu)\n", bits.c_str(), width, + check_width); + } + + result.resize(width); + result.fill(false); + + size_t index = 0; + for (auto nibble_iter = bits.rbegin(); nibble_iter != bits.rend(); ++nibble_iter) { + char nibble = *nibble_iter; + + int value; + if (nibble >= '0' && nibble <= '9') { + value = nibble - '0'; + } else if (nibble >= 'a' && nibble <= 'f') { + value = 10 + (nibble - 'a'); + } else if (nibble >= 'A' && nibble <= 'F') { + value = 10 + (nibble - 'A'); + } else { + log_error("Invalid hex string '%s'?\n", bits.c_str()); + } + NPNR_ASSERT(value >= 0); + NPNR_ASSERT(value < 16); + + // Insert nibble into bitarray. + for (size_t i = 0; i < 4; ++i) { + result.set(index++, (value & (1 << i)) != 0); + } + } + + return result; + } + + private: + Storage storage; +}; + +NEXTPNR_NAMESPACE_END + +#endif /* DYNAMIC_BITARRAY_H */ diff --git a/common/kernel/embed.cc b/common/kernel/embed.cc new file mode 100644 index 00000000..70bbc6fb --- /dev/null +++ b/common/kernel/embed.cc @@ -0,0 +1,49 @@ +#if defined(WIN32) +#include +#endif +#include +#include +#include "embed.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +#if defined(EXTERNAL_CHIPDB_ROOT) + +const void *get_chipdb(const std::string &filename) +{ + static std::map files; + if (!files.count(filename)) { + std::string full_filename = EXTERNAL_CHIPDB_ROOT "/" + filename; + if (boost::filesystem::exists(full_filename)) + files[filename].open(full_filename, boost::iostreams::mapped_file::priv); + } + if (files.count(filename)) + return files.at(filename).data(); + return nullptr; +} + +#elif defined(WIN32) + +const void *get_chipdb(const std::string &filename) +{ + HRSRC rc = ::FindResource(nullptr, filename.c_str(), RT_RCDATA); + HGLOBAL rcData = ::LoadResource(nullptr, rc); + return ::LockResource(rcData); +} + +#else + +EmbeddedFile *EmbeddedFile::head = nullptr; + +const void *get_chipdb(const std::string &filename) +{ + for (EmbeddedFile *file = EmbeddedFile::head; file; file = file->next) + if (file->filename == filename) + return file->content; + return nullptr; +} + +#endif + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/embed.h b/common/kernel/embed.h new file mode 100644 index 00000000..5f2754f8 --- /dev/null +++ b/common/kernel/embed.h @@ -0,0 +1,49 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 whitequark + * + * 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 EMBED_H +#define EMBED_H + +#include "nextpnr.h" +NEXTPNR_NAMESPACE_BEGIN + +#if !defined(EXTERNAL_CHIPDB_ROOT) && !defined(WIN32) + +struct EmbeddedFile +{ + static EmbeddedFile *head; + + std::string filename; + const void *content; + EmbeddedFile *next = nullptr; + + EmbeddedFile(const std::string &filename, const void *content) : filename(filename), content(content) + { + next = head; + head = this; + } +}; + +#endif + +const void *get_chipdb(const std::string &filename); + +NEXTPNR_NAMESPACE_END + +#endif // EMBED_H diff --git a/common/kernel/exclusive_state_groups.h b/common/kernel/exclusive_state_groups.h new file mode 100644 index 00000000..68ce7c4e --- /dev/null +++ b/common/kernel/exclusive_state_groups.h @@ -0,0 +1,154 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 The SymbiFlow Authors. + * + * 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 EXCLUSIVE_STATE_GROUPS_H +#define EXCLUSIVE_STATE_GROUPS_H + +#include +#include +#include +#include +#include + +#include "archdefs.h" +#include "bits.h" +#include "idstring.h" +#include "nextpnr_assertions.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +// Implementation for exclusive state groups, used to implement generic +// constraint system. +template struct ExclusiveStateGroup +{ + ExclusiveStateGroup() : state(kNoSelected) { count.fill(0); } + struct Definition + { + IdString prefix; + IdString default_state; + std::vector states; + }; + + static_assert(StateCount < std::numeric_limits::max(), "StateType cannot store max StateType"); + static_assert(std::numeric_limits::is_signed, "StateType must be signed"); + + std::bitset selected_states; + StateType state; + std::array count; + + static constexpr StateType kNoSelected = -1; + static constexpr StateType kOverConstrained = -2; + + std::pair current_state(const Definition &definition) const + { + if (state <= 0) { + return std::make_pair(state == kNoSelected, definition.default_state); + } else { + NPNR_ASSERT(state <= definition.states.size()); + return std::make_pair(true, definition.states[state]); + } + } + + bool check_implies(int32_t next_state) const + { + // Implies can be satified if either that state is + // selected, or no state is currently selected. + return state == next_state || state == kNoSelected; + } + + bool add_implies(int32_t next_state) + { + NPNR_ASSERT(next_state >= 0 && (size_t)next_state < StateCount); + + // Increment and mark the state as selected. + count[next_state] += 1; + selected_states[next_state] = true; + + if (state == next_state) { + // State was already selected, state group is still satified. + return true; + } else if (selected_states.count() == 1) { + // State was not select selected, state is now selected. + // State group is satified. + state = next_state; + return true; + } else { + // State group is now overconstrained. + state = kOverConstrained; + return false; + } + }; + + void remove_implies(int32_t next_state) + { + NPNR_ASSERT(next_state >= 0 && (size_t)next_state < StateCount); + NPNR_ASSERT(selected_states[next_state]); + + count[next_state] -= 1; + NPNR_ASSERT(count[next_state] >= 0); + + // Check if next_state is now unselected. + if (count[next_state] == 0) { + // next_state is not longer selected + selected_states[next_state] = false; + + // Check whether the state group is now unselected or satified. + auto value = selected_states.to_ulong(); + auto number_selected = Bits::popcount(value); + if (number_selected == 1) { + // Group is no longer overconstrained. + state = Bits::ctz(value); + NPNR_ASSERT(selected_states[state]); + } else if (number_selected == 0) { + // Group is unselected. + state = kNoSelected; + } else { + state = kOverConstrained; + } + } + } + + template bool requires(const StateRange &state_range) const + { + if (state < 0) { + return false; + } + + for (const auto required_state : state_range) { + if (state == required_state) { + return true; + } + } + + return false; + } + + void print_debug(const Context *ctx, IdString object, const Definition &definition) const; + void explain_implies(const Context *ctx, IdString object, IdString cell, const Definition &definition, BelId bel, + int32_t next_state) const; + + template + void explain_requires(const Context *ctx, IdString object, IdString cell, const Definition &definition, BelId bel, + const StateRange state_range) const; +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/kernel/exclusive_state_groups.impl.h b/common/kernel/exclusive_state_groups.impl.h new file mode 100644 index 00000000..f3ddb5fd --- /dev/null +++ b/common/kernel/exclusive_state_groups.impl.h @@ -0,0 +1,89 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 The SymbiFlow Authors. + * + * 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. + * + */ + +#pragma once + +#include "context.h" +#include "exclusive_state_groups.h" +#include "log.h" + +NEXTPNR_NAMESPACE_BEGIN + +template +void ExclusiveStateGroup::print_debug(const Context *ctx, IdString object, + const Definition &definition) const +{ + if (state == kNoSelected) { + NPNR_ASSERT(selected_states.count() == 0); + log_info("%s.%s is currently unselected\n", object.c_str(ctx), definition.prefix.c_str(ctx)); + } else if (state >= 0) { + log_info("%s.%s = %s, count = %d\n", object.c_str(ctx), definition.prefix.c_str(ctx), + definition.states.at(state).c_str(ctx), count[state]); + } else { + NPNR_ASSERT(state == kOverConstrained); + log_info("%s.%s is currently overconstrained, states selected:\n", object.c_str(ctx), + definition.prefix.c_str(ctx)); + for (size_t i = 0; i < definition.states.size(); ++i) { + if (selected_states[i]) { + log_info(" - %s, count = %d\n", definition.states.at(i).c_str(ctx), count[i]); + } + } + } +} + +template +void ExclusiveStateGroup::explain_implies(const Context *ctx, IdString object, + IdString cell, const Definition &definition, + BelId bel, int32_t next_state) const +{ + if (check_implies(next_state)) { + log_info("Placing cell %s at bel %s does not violate %s.%s\n", cell.c_str(ctx), ctx->nameOfBel(bel), + object.c_str(ctx), definition.prefix.c_str(ctx)); + } else { + log_info("Placing cell %s at bel %s does violates %s.%s, desired state = %s.\n", cell.c_str(ctx), + ctx->nameOfBel(bel), object.c_str(ctx), definition.prefix.c_str(ctx), + definition.states.at(next_state).c_str(ctx)); + print_debug(ctx, object, definition); + } +} + +template +template +void ExclusiveStateGroup::explain_requires(const Context *ctx, IdString object, + IdString cell, + const Definition &definition, BelId bel, + const StateRange state_range) const +{ + if (requires(state_range)) { + log_info("Placing cell %s at bel %s does not violate %s.%s\n", cell.c_str(ctx), ctx->nameOfBel(bel), + object.c_str(ctx), definition.prefix.c_str(ctx)); + } else { + log_info("Placing cell %s at bel %s does violate %s.%s, because current state is %s, constraint requires one " + "of:\n", + cell.c_str(ctx), ctx->nameOfBel(bel), object.c_str(ctx), definition.prefix.c_str(ctx), + state != -1 ? definition.states.at(state).c_str(ctx) : "unset"); + + for (const auto required_state : state_range) { + log_info(" - %s\n", definition.states.at(required_state).c_str(ctx)); + } + print_debug(ctx, object, definition); + } +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/handle_error.cc b/common/kernel/handle_error.cc new file mode 100644 index 00000000..d5542369 --- /dev/null +++ b/common/kernel/handle_error.cc @@ -0,0 +1,61 @@ +#ifndef NO_PYTHON + +#include +#include +#include "nextpnr.h" + +namespace py = pybind11; + +NEXTPNR_NAMESPACE_BEGIN + +// Parses the value of the active python exception +// NOTE SHOULD NOT BE CALLED IF NO EXCEPTION +std::string parse_python_exception() +{ + PyObject *type_ptr = NULL, *value_ptr = NULL, *traceback_ptr = NULL; + // Fetch the exception info from the Python C API + PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr); + + // Fallback error + std::string ret("Unfetchable Python error"); + // If the fetch got a type pointer, parse the type into the exception string + if (type_ptr != NULL) { + py::object obj = py::reinterpret_borrow(type_ptr); + // If a valid string extraction is available, use it + // otherwise use fallback + if (py::isinstance(obj)) + ret = obj.cast(); + else + ret = "Unknown exception type"; + } + // Do the same for the exception value (the stringification of the + // exception) + if (value_ptr != NULL) { + py::object obj = py::reinterpret_borrow(value_ptr); + if (py::isinstance(obj)) + ret += ": " + obj.cast(); + else + ret += std::string(": Unparseable Python error: "); + } + // Parse lines from the traceback using the Python traceback module + if (traceback_ptr != NULL) { + py::handle h_tb(traceback_ptr); + // Load the traceback module and the format_tb function + py::object tb(py::module::import("traceback")); + py::object fmt_tb(tb.attr("format_tb")); + // Call format_tb to get a list of traceback strings + py::object tb_list(fmt_tb(h_tb)); + // Join the traceback strings into a single string + py::object tb_str(py::str("\n") + tb_list); + // Extract the string, check the extraction, and fallback in necessary + if (py::isinstance(tb_str)) + ret += ": " + tb_str.cast(); + else + ret += std::string(": Unparseable Python traceback"); + } + return ret; +} + +NEXTPNR_NAMESPACE_END + +#endif // NO_PYTHON \ No newline at end of file diff --git a/common/kernel/hashlib.h b/common/kernel/hashlib.h new file mode 100644 index 00000000..2f7357e2 --- /dev/null +++ b/common/kernel/hashlib.h @@ -0,0 +1,1210 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. + +// ------------------------------------------------------- +// Written by Claire Xen in 2014 +// ------------------------------------------------------- + +#ifndef HASHLIB_H +#define HASHLIB_H + +#include +#include +#include +#include +#include + +#include "nextpnr_assertions.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +const int hashtable_size_trigger = 2; +const int hashtable_size_factor = 3; + +// Cantor pairing function for two non-negative integers +// https://en.wikipedia.org/wiki/Pairing_function +inline unsigned int mkhash(unsigned int a, unsigned int b) { return (a * a + 3 * a + 2 * a * b + b + b * b) / 2; } + +// traditionally 5381 is used as starting value for the djb2 hash +const unsigned int mkhash_init = 5381; + +// The ADD version of DJB2 +// (use this version for cache locality in b) +inline unsigned int mkhash_add(unsigned int a, unsigned int b) { return ((a << 5) + a) + b; } + +inline unsigned int mkhash_xorshift(unsigned int a) +{ + if (sizeof(a) == 4) { + a ^= a << 13; + a ^= a >> 17; + a ^= a << 5; + } else if (sizeof(a) == 8) { + a ^= a << 13; + a ^= a >> 7; + a ^= a << 17; + } else + NPNR_ASSERT_FALSE("mkhash_xorshift() only implemented for 32 bit and 64 bit ints"); + return a; +} + +template struct hash_ops +{ + static inline bool cmp(const T &a, const T &b) { return a == b; } + static inline unsigned int hash(const T &a) { return a.hash(); } +}; + +struct hash_int_ops +{ + template static inline bool cmp(T a, T b) { return a == b; } +}; + +template <> struct hash_ops : hash_int_ops +{ + static inline unsigned int hash(bool a) { return a ? 1 : 0; } +}; +template <> struct hash_ops : hash_int_ops +{ + static inline unsigned int hash(int32_t a) { return a; } +}; +template <> struct hash_ops : hash_int_ops +{ + static inline unsigned int hash(int64_t a) { return mkhash((unsigned int)(a), (unsigned int)(a >> 32)); } +}; + +template <> struct hash_ops : hash_int_ops +{ + static inline unsigned int hash(uint32_t a) { return a; } +}; +template <> struct hash_ops : hash_int_ops +{ + static inline unsigned int hash(uint64_t a) { return mkhash((unsigned int)(a), (unsigned int)(a >> 32)); } +}; + +template <> struct hash_ops +{ + static inline bool cmp(const std::string &a, const std::string &b) { return a == b; } + static inline unsigned int hash(const std::string &a) + { + unsigned int v = 0; + for (auto c : a) + v = mkhash(v, c); + return v; + } +}; + +template struct hash_ops> +{ + static inline bool cmp(std::pair a, std::pair b) { return a == b; } + static inline unsigned int hash(std::pair a) + { + return mkhash(hash_ops

::hash(a.first), hash_ops::hash(a.second)); + } +}; + +template struct hash_ops> +{ + static inline bool cmp(std::tuple a, std::tuple b) { return a == b; } + template + static inline typename std::enable_if::type hash(std::tuple) + { + return mkhash_init; + } + template + static inline typename std::enable_if::type hash(std::tuple a) + { + typedef hash_ops>::type> element_ops_t; + return mkhash(hash(a), element_ops_t::hash(std::get(a))); + } +}; + +template struct hash_ops> +{ + static inline bool cmp(std::vector a, std::vector b) { return a == b; } + static inline unsigned int hash(std::vector a) + { + unsigned int h = mkhash_init; + for (auto k : a) + h = mkhash(h, hash_ops::hash(k)); + return h; + } +}; + +template struct hash_ops> +{ + static inline bool cmp(std::array a, std::array b) { return a == b; } + static inline unsigned int hash(std::array a) + { + unsigned int h = mkhash_init; + for (auto k : a) + h = mkhash(h, hash_ops::hash(k)); + return h; + } +}; + +struct hash_cstr_ops +{ + static inline bool cmp(const char *a, const char *b) + { + for (int i = 0; a[i] || b[i]; i++) + if (a[i] != b[i]) + return false; + return true; + } + static inline unsigned int hash(const char *a) + { + unsigned int hash = mkhash_init; + while (*a) + hash = mkhash(hash, *(a++)); + return hash; + } +}; + +struct hash_ptr_ops +{ + static inline bool cmp(const void *a, const void *b) { return a == b; } + static inline unsigned int hash(const void *a) { return (uintptr_t)a; } +}; + +struct hash_obj_ops +{ + static inline bool cmp(const void *a, const void *b) { return a == b; } + template static inline unsigned int hash(const T *a) { return a ? a->hash() : 0; } +}; + +template inline unsigned int mkhash(const T &v) { return hash_ops().hash(v); } + +inline int hashtable_size(int min_size) +{ + static std::vector zero_and_some_primes = { + 0, 23, 29, 37, 47, 59, 79, 101, 127, 163, + 211, 269, 337, 431, 541, 677, 853, 1069, 1361, 1709, + 2137, 2677, 3347, 4201, 5261, 6577, 8231, 10289, 12889, 16127, + 20161, 25219, 31531, 39419, 49277, 61603, 77017, 96281, 120371, 150473, + 188107, 235159, 293957, 367453, 459317, 574157, 717697, 897133, 1121423, 1401791, + 1752239, 2190299, 2737937, 3422429, 4278037, 5347553, 6684443, 8355563, 10444457, 13055587, + 16319519, 20399411, 25499291, 31874149, 39842687, 49803361, 62254207, 77817767, 97272239, 121590311, + 151987889, 189984863, 237481091, 296851369, 371064217}; + + for (auto p : zero_and_some_primes) + if (p >= min_size) + return p; + + if (sizeof(int) == 4) + throw std::length_error("hash table exceeded maximum size. use a ILP64 abi for larger tables."); + + for (auto p : zero_and_some_primes) + if (100129 * p > min_size) + return 100129 * p; + + throw std::length_error("hash table exceeded maximum size."); +} + +template > class dict; +template > class idict; +template > class pool; +template > class mfp; + +template class dict +{ + struct entry_t + { + std::pair udata; + int next; + + entry_t() {} + entry_t(const std::pair &udata, int next) : udata(udata), next(next) {} + entry_t(std::pair &&udata, int next) : udata(std::move(udata)), next(next) {} + bool operator<(const entry_t &other) const { return udata.first < other.udata.first; } + }; + + std::vector hashtable; + std::vector entries; + OPS ops; + +#ifdef NDEBUG + static inline void do_assert(bool) {} +#else + static inline void do_assert(bool cond) { NPNR_ASSERT(cond); } +#endif + + int do_hash(const K &key) const + { + unsigned int hash = 0; + if (!hashtable.empty()) + hash = ops.hash(key) % (unsigned int)(hashtable.size()); + return hash; + } + + void do_rehash() + { + hashtable.clear(); + hashtable.resize(hashtable_size(entries.capacity() * hashtable_size_factor), -1); + + for (int i = 0; i < int(entries.size()); i++) { + do_assert(-1 <= entries[i].next && entries[i].next < int(entries.size())); + int hash = do_hash(entries[i].udata.first); + entries[i].next = hashtable[hash]; + hashtable[hash] = i; + } + } + + int do_erase(int index, int hash) + { + do_assert(index < int(entries.size())); + if (hashtable.empty() || index < 0) + return 0; + + int k = hashtable[hash]; + do_assert(0 <= k && k < int(entries.size())); + + if (k == index) { + hashtable[hash] = entries[index].next; + } else { + while (entries[k].next != index) { + k = entries[k].next; + do_assert(0 <= k && k < int(entries.size())); + } + entries[k].next = entries[index].next; + } + + int back_idx = entries.size() - 1; + + if (index != back_idx) { + int back_hash = do_hash(entries[back_idx].udata.first); + + k = hashtable[back_hash]; + do_assert(0 <= k && k < int(entries.size())); + + if (k == back_idx) { + hashtable[back_hash] = index; + } else { + while (entries[k].next != back_idx) { + k = entries[k].next; + do_assert(0 <= k && k < int(entries.size())); + } + entries[k].next = index; + } + + entries[index] = std::move(entries[back_idx]); + } + + entries.pop_back(); + + if (entries.empty()) + hashtable.clear(); + + return 1; + } + + int do_lookup(const K &key, int &hash) const + { + if (hashtable.empty()) + return -1; + + if (entries.size() * hashtable_size_trigger > hashtable.size()) { + ((dict *)this)->do_rehash(); + hash = do_hash(key); + } + + int index = hashtable[hash]; + + while (index >= 0 && !ops.cmp(entries[index].udata.first, key)) { + index = entries[index].next; + do_assert(-1 <= index && index < int(entries.size())); + } + + return index; + } + + int do_insert(const K &key, int &hash) + { + if (hashtable.empty()) { + entries.emplace_back(std::pair(key, T()), -1); + do_rehash(); + hash = do_hash(key); + } else { + entries.emplace_back(std::pair(key, T()), hashtable[hash]); + hashtable[hash] = entries.size() - 1; + } + return entries.size() - 1; + } + + int do_insert(const std::pair &value, int &hash) + { + if (hashtable.empty()) { + entries.emplace_back(value, -1); + do_rehash(); + hash = do_hash(value.first); + } else { + entries.emplace_back(value, hashtable[hash]); + hashtable[hash] = entries.size() - 1; + } + return entries.size() - 1; + } + + int do_insert(std::pair &&rvalue, int &hash) + { + if (hashtable.empty()) { + auto key = rvalue.first; + entries.emplace_back(std::forward>(rvalue), -1); + do_rehash(); + hash = do_hash(key); + } else { + entries.emplace_back(std::forward>(rvalue), hashtable[hash]); + hashtable[hash] = entries.size() - 1; + } + return entries.size() - 1; + } + + public: + using key_type = K; + using mapped_type = T; + using value_type = std::pair; + + class const_iterator : public std::iterator> + { + friend class dict; + + protected: + const dict *ptr; + int index; + const_iterator(const dict *ptr, int index) : ptr(ptr), index(index) {} + + public: + const_iterator() {} + const_iterator operator++() + { + index--; + return *this; + } + const_iterator operator+=(int amt) + { + index -= amt; + return *this; + } + bool operator<(const const_iterator &other) const { return index > other.index; } + bool operator==(const const_iterator &other) const { return index == other.index; } + bool operator!=(const const_iterator &other) const { return index != other.index; } + const std::pair &operator*() const { return ptr->entries[index].udata; } + const std::pair *operator->() const { return &ptr->entries[index].udata; } + }; + + class iterator : public std::iterator> + { + friend class dict; + + protected: + dict *ptr; + int index; + iterator(dict *ptr, int index) : ptr(ptr), index(index) {} + + public: + iterator() {} + iterator operator++() + { + index--; + return *this; + } + iterator operator+=(int amt) + { + index -= amt; + return *this; + } + bool operator<(const iterator &other) const { return index > other.index; } + bool operator==(const iterator &other) const { return index == other.index; } + bool operator!=(const iterator &other) const { return index != other.index; } + std::pair &operator*() { return ptr->entries[index].udata; } + std::pair *operator->() { return &ptr->entries[index].udata; } + const std::pair &operator*() const { return ptr->entries[index].udata; } + const std::pair *operator->() const { return &ptr->entries[index].udata; } + operator const_iterator() const { return const_iterator(ptr, index); } + }; + + dict() {} + + dict(const dict &other) + { + entries = other.entries; + do_rehash(); + } + + dict(dict &&other) { swap(other); } + + dict &operator=(const dict &other) + { + entries = other.entries; + do_rehash(); + return *this; + } + + dict &operator=(dict &&other) + { + clear(); + swap(other); + return *this; + } + + dict(const std::initializer_list> &list) + { + for (auto &it : list) + insert(it); + } + + template dict(InputIterator first, InputIterator last) { insert(first, last); } + + template void insert(InputIterator first, InputIterator last) + { + for (; first != last; ++first) + insert(*first); + } + + std::pair insert(const K &key) + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + if (i >= 0) + return std::pair(iterator(this, i), false); + i = do_insert(key, hash); + return std::pair(iterator(this, i), true); + } + + std::pair insert(const std::pair &value) + { + int hash = do_hash(value.first); + int i = do_lookup(value.first, hash); + if (i >= 0) + return std::pair(iterator(this, i), false); + i = do_insert(value, hash); + return std::pair(iterator(this, i), true); + } + + std::pair insert(std::pair &&rvalue) + { + int hash = do_hash(rvalue.first); + int i = do_lookup(rvalue.first, hash); + if (i >= 0) + return std::pair(iterator(this, i), false); + i = do_insert(std::forward>(rvalue), hash); + return std::pair(iterator(this, i), true); + } + + std::pair emplace(K const &key, T const &value) + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + if (i >= 0) + return std::pair(iterator(this, i), false); + i = do_insert(std::make_pair(key, value), hash); + return std::pair(iterator(this, i), true); + } + + std::pair emplace(K const &key, T &&rvalue) + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + if (i >= 0) + return std::pair(iterator(this, i), false); + i = do_insert(std::make_pair(key, std::forward(rvalue)), hash); + return std::pair(iterator(this, i), true); + } + + std::pair emplace(K &&rkey, T const &value) + { + int hash = do_hash(rkey); + int i = do_lookup(rkey, hash); + if (i >= 0) + return std::pair(iterator(this, i), false); + i = do_insert(std::make_pair(std::forward(rkey), value), hash); + return std::pair(iterator(this, i), true); + } + + std::pair emplace(K &&rkey, T &&rvalue) + { + int hash = do_hash(rkey); + int i = do_lookup(rkey, hash); + if (i >= 0) + return std::pair(iterator(this, i), false); + i = do_insert(std::make_pair(std::forward(rkey), std::forward(rvalue)), hash); + return std::pair(iterator(this, i), true); + } + + int erase(const K &key) + { + int hash = do_hash(key); + int index = do_lookup(key, hash); + return do_erase(index, hash); + } + + iterator erase(iterator it) + { + int hash = do_hash(it->first); + do_erase(it.index, hash); + return ++it; + } + + int count(const K &key) const + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + return i < 0 ? 0 : 1; + } + + int count(const K &key, const_iterator it) const + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + return i < 0 || i > it.index ? 0 : 1; + } + + iterator find(const K &key) + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + if (i < 0) + return end(); + return iterator(this, i); + } + + const_iterator find(const K &key) const + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + if (i < 0) + return end(); + return const_iterator(this, i); + } + + T &at(const K &key) + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + if (i < 0) + throw std::out_of_range("dict::at()"); + return entries[i].udata.second; + } + + const T &at(const K &key) const + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + if (i < 0) + throw std::out_of_range("dict::at()"); + return entries[i].udata.second; + } + + const T &at(const K &key, const T &defval) const + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + if (i < 0) + return defval; + return entries[i].udata.second; + } + + T &operator[](const K &key) + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + if (i < 0) + i = do_insert(std::pair(key, T()), hash); + return entries[i].udata.second; + } + + template > void sort(Compare comp = Compare()) + { + std::sort(entries.begin(), entries.end(), + [comp](const entry_t &a, const entry_t &b) { return comp(b.udata.first, a.udata.first); }); + do_rehash(); + } + + void swap(dict &other) + { + hashtable.swap(other.hashtable); + entries.swap(other.entries); + } + + bool operator==(const dict &other) const + { + if (size() != other.size()) + return false; + for (auto &it : entries) { + auto oit = other.find(it.udata.first); + if (oit == other.end() || !(oit->second == it.udata.second)) + return false; + } + return true; + } + + bool operator!=(const dict &other) const { return !operator==(other); } + + unsigned int hash() const + { + unsigned int h = mkhash_init; + for (auto &entry : entries) { + h ^= hash_ops::hash(entry.udata.first); + h ^= hash_ops::hash(entry.udata.second); + } + return h; + } + + void reserve(size_t n) { entries.reserve(n); } + size_t size() const { return entries.size(); } + bool empty() const { return entries.empty(); } + void clear() + { + hashtable.clear(); + entries.clear(); + } + + iterator begin() { return iterator(this, int(entries.size()) - 1); } + iterator element(int n) { return iterator(this, int(entries.size()) - 1 - n); } + iterator end() { return iterator(nullptr, -1); } + + const_iterator begin() const { return const_iterator(this, int(entries.size()) - 1); } + const_iterator element(int n) const { return const_iterator(this, int(entries.size()) - 1 - n); } + const_iterator end() const { return const_iterator(nullptr, -1); } +}; + +template class pool +{ + template friend class idict; + + protected: + struct entry_t + { + K udata; + int next; + + entry_t() {} + entry_t(const K &udata, int next) : udata(udata), next(next) {} + entry_t(K &&udata, int next) : udata(std::move(udata)), next(next) {} + }; + + std::vector hashtable; + std::vector entries; + OPS ops; + +#ifdef NDEBUG + static inline void do_assert(bool) {} +#else + static inline void do_assert(bool cond) { NPNR_ASSERT(cond); } +#endif + + int do_hash(const K &key) const + { + unsigned int hash = 0; + if (!hashtable.empty()) + hash = ops.hash(key) % (unsigned int)(hashtable.size()); + return hash; + } + + void do_rehash() + { + hashtable.clear(); + hashtable.resize(hashtable_size(entries.capacity() * hashtable_size_factor), -1); + + for (int i = 0; i < int(entries.size()); i++) { + do_assert(-1 <= entries[i].next && entries[i].next < int(entries.size())); + int hash = do_hash(entries[i].udata); + entries[i].next = hashtable[hash]; + hashtable[hash] = i; + } + } + + int do_erase(int index, int hash) + { + do_assert(index < int(entries.size())); + if (hashtable.empty() || index < 0) + return 0; + + int k = hashtable[hash]; + if (k == index) { + hashtable[hash] = entries[index].next; + } else { + while (entries[k].next != index) { + k = entries[k].next; + do_assert(0 <= k && k < int(entries.size())); + } + entries[k].next = entries[index].next; + } + + int back_idx = entries.size() - 1; + + if (index != back_idx) { + int back_hash = do_hash(entries[back_idx].udata); + + k = hashtable[back_hash]; + if (k == back_idx) { + hashtable[back_hash] = index; + } else { + while (entries[k].next != back_idx) { + k = entries[k].next; + do_assert(0 <= k && k < int(entries.size())); + } + entries[k].next = index; + } + + entries[index] = std::move(entries[back_idx]); + } + + entries.pop_back(); + + if (entries.empty()) + hashtable.clear(); + + return 1; + } + + int do_lookup(const K &key, int &hash) const + { + if (hashtable.empty()) + return -1; + + if (entries.size() * hashtable_size_trigger > hashtable.size()) { + ((pool *)this)->do_rehash(); + hash = do_hash(key); + } + + int index = hashtable[hash]; + + while (index >= 0 && !ops.cmp(entries[index].udata, key)) { + index = entries[index].next; + do_assert(-1 <= index && index < int(entries.size())); + } + + return index; + } + + int do_insert(const K &value, int &hash) + { + if (hashtable.empty()) { + entries.emplace_back(value, -1); + do_rehash(); + hash = do_hash(value); + } else { + entries.emplace_back(value, hashtable[hash]); + hashtable[hash] = entries.size() - 1; + } + return entries.size() - 1; + } + + int do_insert(K &&rvalue, int &hash) + { + if (hashtable.empty()) { + entries.emplace_back(std::forward(rvalue), -1); + do_rehash(); + hash = do_hash(rvalue); + } else { + entries.emplace_back(std::forward(rvalue), hashtable[hash]); + hashtable[hash] = entries.size() - 1; + } + return entries.size() - 1; + } + + public: + class const_iterator : public std::iterator + { + friend class pool; + + protected: + const pool *ptr; + int index; + const_iterator(const pool *ptr, int index) : ptr(ptr), index(index) {} + + public: + const_iterator() {} + const_iterator operator++() + { + index--; + return *this; + } + bool operator==(const const_iterator &other) const { return index == other.index; } + bool operator!=(const const_iterator &other) const { return index != other.index; } + const K &operator*() const { return ptr->entries[index].udata; } + const K *operator->() const { return &ptr->entries[index].udata; } + }; + + class iterator : public std::iterator + { + friend class pool; + + protected: + pool *ptr; + int index; + iterator(pool *ptr, int index) : ptr(ptr), index(index) {} + + public: + iterator() {} + iterator operator++() + { + index--; + return *this; + } + bool operator==(const iterator &other) const { return index == other.index; } + bool operator!=(const iterator &other) const { return index != other.index; } + K &operator*() { return ptr->entries[index].udata; } + K *operator->() { return &ptr->entries[index].udata; } + const K &operator*() const { return ptr->entries[index].udata; } + const K *operator->() const { return &ptr->entries[index].udata; } + operator const_iterator() const { return const_iterator(ptr, index); } + }; + + pool() {} + + pool(const pool &other) + { + entries = other.entries; + do_rehash(); + } + + pool(pool &&other) { swap(other); } + + pool &operator=(const pool &other) + { + entries = other.entries; + do_rehash(); + return *this; + } + + pool &operator=(pool &&other) + { + clear(); + swap(other); + return *this; + } + + pool(const std::initializer_list &list) + { + for (auto &it : list) + insert(it); + } + + template pool(InputIterator first, InputIterator last) { insert(first, last); } + + template void insert(InputIterator first, InputIterator last) + { + for (; first != last; ++first) + insert(*first); + } + + std::pair insert(const K &value) + { + int hash = do_hash(value); + int i = do_lookup(value, hash); + if (i >= 0) + return std::pair(iterator(this, i), false); + i = do_insert(value, hash); + return std::pair(iterator(this, i), true); + } + + std::pair insert(K &&rvalue) + { + int hash = do_hash(rvalue); + int i = do_lookup(rvalue, hash); + if (i >= 0) + return std::pair(iterator(this, i), false); + i = do_insert(std::forward(rvalue), hash); + return std::pair(iterator(this, i), true); + } + + template std::pair emplace(Args &&...args) + { + return insert(K(std::forward(args)...)); + } + + int erase(const K &key) + { + int hash = do_hash(key); + int index = do_lookup(key, hash); + return do_erase(index, hash); + } + + iterator erase(iterator it) + { + int hash = do_hash(*it); + do_erase(it.index, hash); + return ++it; + } + + int count(const K &key) const + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + return i < 0 ? 0 : 1; + } + + int count(const K &key, const_iterator it) const + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + return i < 0 || i > it.index ? 0 : 1; + } + + iterator find(const K &key) + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + if (i < 0) + return end(); + return iterator(this, i); + } + + const_iterator find(const K &key) const + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + if (i < 0) + return end(); + return const_iterator(this, i); + } + + bool operator[](const K &key) + { + int hash = do_hash(key); + int i = do_lookup(key, hash); + return i >= 0; + } + + template > void sort(Compare comp = Compare()) + { + std::sort(entries.begin(), entries.end(), + [comp](const entry_t &a, const entry_t &b) { return comp(b.udata, a.udata); }); + do_rehash(); + } + + K pop() + { + iterator it = begin(); + K ret = *it; + erase(it); + return ret; + } + + void swap(pool &other) + { + hashtable.swap(other.hashtable); + entries.swap(other.entries); + } + + bool operator==(const pool &other) const + { + if (size() != other.size()) + return false; + for (auto &it : entries) + if (!other.count(it.udata)) + return false; + return true; + } + + bool operator!=(const pool &other) const { return !operator==(other); } + + bool hash() const + { + unsigned int hashval = mkhash_init; + for (auto &it : entries) + hashval ^= ops.hash(it.udata); + return hashval; + } + + void reserve(size_t n) { entries.reserve(n); } + size_t size() const { return entries.size(); } + bool empty() const { return entries.empty(); } + void clear() + { + hashtable.clear(); + entries.clear(); + } + + iterator begin() { return iterator(this, int(entries.size()) - 1); } + iterator element(int n) { return iterator(this, int(entries.size()) - 1 - n); } + iterator end() { return iterator(nullptr, -1); } + + const_iterator begin() const { return const_iterator(this, int(entries.size()) - 1); } + const_iterator element(int n) const { return const_iterator(this, int(entries.size()) - 1 - n); } + const_iterator end() const { return const_iterator(nullptr, -1); } +}; + +template class idict +{ + pool database; + + public: + class const_iterator : public std::iterator + { + friend class idict; + + protected: + const idict &container; + int index; + const_iterator(const idict &container, int index) : container(container), index(index) {} + + public: + const_iterator() {} + const_iterator operator++() + { + index++; + return *this; + } + bool operator==(const const_iterator &other) const { return index == other.index; } + bool operator!=(const const_iterator &other) const { return index != other.index; } + const K &operator*() const { return container[index]; } + const K *operator->() const { return &container[index]; } + }; + + int operator()(const K &key) + { + int hash = database.do_hash(key); + int i = database.do_lookup(key, hash); + if (i < 0) + i = database.do_insert(key, hash); + return i + offset; + } + + int at(const K &key) const + { + int hash = database.do_hash(key); + int i = database.do_lookup(key, hash); + if (i < 0) + throw std::out_of_range("idict::at()"); + return i + offset; + } + + int at(const K &key, int defval) const + { + int hash = database.do_hash(key); + int i = database.do_lookup(key, hash); + if (i < 0) + return defval; + return i + offset; + } + + int count(const K &key) const + { + int hash = database.do_hash(key); + int i = database.do_lookup(key, hash); + return i < 0 ? 0 : 1; + } + + void expect(const K &key, int i) + { + int j = (*this)(key); + if (i != j) + throw std::out_of_range("idict::expect()"); + } + + const K &operator[](int index) const { return database.entries.at(index - offset).udata; } + + void swap(idict &other) { database.swap(other.database); } + + void reserve(size_t n) { database.reserve(n); } + size_t size() const { return database.size(); } + bool empty() const { return database.empty(); } + void clear() { database.clear(); } + + const_iterator begin() const { return const_iterator(*this, offset); } + const_iterator element(int n) const { return const_iterator(*this, n); } + const_iterator end() const { return const_iterator(*this, offset + size()); } +}; + +template class mfp +{ + mutable idict database; + mutable std::vector parents; + + public: + typedef typename idict::const_iterator const_iterator; + + int operator()(const K &key) const + { + int i = database(key); + parents.resize(database.size(), -1); + return i; + } + + const K &operator[](int index) const { return database[index]; } + + int ifind(int i) const + { + int p = i, k = i; + + while (parents[p] != -1) + p = parents[p]; + + while (k != p) { + int next_k = parents[k]; + parents[k] = p; + k = next_k; + } + + return p; + } + + void imerge(int i, int j) + { + i = ifind(i); + j = ifind(j); + + if (i != j) + parents[i] = j; + } + + void ipromote(int i) + { + int k = i; + + while (k != -1) { + int next_k = parents[k]; + parents[k] = i; + k = next_k; + } + + parents[i] = -1; + } + + int lookup(const K &a) const { return ifind((*this)(a)); } + + const K &find(const K &a) const + { + int i = database.at(a, -1); + if (i < 0) + return a; + return (*this)[ifind(i)]; + } + + void merge(const K &a, const K &b) { imerge((*this)(a), (*this)(b)); } + + void promote(const K &a) + { + int i = database.at(a, -1); + if (i >= 0) + ipromote(i); + } + + void swap(mfp &other) + { + database.swap(other.database); + parents.swap(other.parents); + } + + void reserve(size_t n) { database.reserve(n); } + size_t size() const { return database.size(); } + bool empty() const { return database.empty(); } + void clear() + { + database.clear(); + parents.clear(); + } + + const_iterator begin() const { return database.begin(); } + const_iterator element(int n) const { return database.element(n); } + const_iterator end() const { return database.end(); } +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/kernel/idstring.cc b/common/kernel/idstring.cc new file mode 100644 index 00000000..9e27ac6f --- /dev/null +++ b/common/kernel/idstring.cc @@ -0,0 +1,51 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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 "idstring.h" + +#include "basectx.h" + +NEXTPNR_NAMESPACE_BEGIN + +void IdString::set(const BaseCtx *ctx, const std::string &s) +{ + auto it = ctx->idstring_str_to_idx->find(s); + if (it == ctx->idstring_str_to_idx->end()) { + index = ctx->idstring_idx_to_str->size(); + auto insert_rc = ctx->idstring_str_to_idx->insert({s, index}); + ctx->idstring_idx_to_str->push_back(&insert_rc.first->first); + } else { + index = it->second; + } +} + +const std::string &IdString::str(const BaseCtx *ctx) const { return *ctx->idstring_idx_to_str->at(index); } + +const char *IdString::c_str(const BaseCtx *ctx) const { return str(ctx).c_str(); } + +void IdString::initialize_add(const BaseCtx *ctx, const char *s, int idx) +{ + NPNR_ASSERT(ctx->idstring_str_to_idx->count(s) == 0); + NPNR_ASSERT(int(ctx->idstring_idx_to_str->size()) == idx); + auto insert_rc = ctx->idstring_str_to_idx->insert({s, idx}); + ctx->idstring_idx_to_str->push_back(&insert_rc.first->first); +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/idstring.h b/common/kernel/idstring.h new file mode 100644 index 00000000..019e0a2a --- /dev/null +++ b/common/kernel/idstring.h @@ -0,0 +1,75 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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 IDSTRING_H +#define IDSTRING_H + +#include +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct BaseCtx; + +struct IdString +{ + int index; + + static void initialize_arch(const BaseCtx *ctx); + + static void initialize_add(const BaseCtx *ctx, const char *s, int idx); + + constexpr IdString() : index(0) {} + explicit constexpr IdString(int index) : index(index) {} + + void set(const BaseCtx *ctx, const std::string &s); + + IdString(const BaseCtx *ctx, const std::string &s) { set(ctx, s); } + + IdString(const BaseCtx *ctx, const char *s) { set(ctx, s); } + + const std::string &str(const BaseCtx *ctx) const; + + const char *c_str(const BaseCtx *ctx) const; + + bool operator<(const IdString &other) const { return index < other.index; } + + bool operator==(const IdString &other) const { return index == other.index; } + + bool operator!=(const IdString &other) const { return index != other.index; } + + bool empty() const { return index == 0; } + + unsigned int hash() const { return index; } + + template bool in(Args... args) const + { + // Credit: https://articles.emptycrate.com/2016/05/14/folds_in_cpp11_ish.html + bool result = false; + (void)std::initializer_list{(result = result || in(args), 0)...}; + return result; + } + + bool in(const IdString &rhs) const { return *this == rhs; } +}; + +NEXTPNR_NAMESPACE_END + +#endif /* IDSTRING_H */ diff --git a/common/kernel/idstringlist.cc b/common/kernel/idstringlist.cc new file mode 100644 index 00000000..624622cf --- /dev/null +++ b/common/kernel/idstringlist.cc @@ -0,0 +1,80 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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 "idstringlist.h" +#include "context.h" + +NEXTPNR_NAMESPACE_BEGIN + +IdStringList IdStringList::parse(Context *ctx, const std::string &str) +{ + char delim = ctx->getNameDelimiter(); + size_t id_count = std::count(str.begin(), str.end(), delim) + 1; + IdStringList list(id_count); + size_t start = 0; + for (size_t i = 0; i < id_count; i++) { + size_t end = str.find(delim, start); + NPNR_ASSERT((i == (id_count - 1)) || (end != std::string::npos)); + list.ids[i] = ctx->id(str.substr(start, end - start)); + start = end + 1; + } + return list; +} + +void IdStringList::build_str(const Context *ctx, std::string &str) const +{ + char delim = ctx->getNameDelimiter(); + bool first = true; + str.clear(); + for (auto entry : ids) { + if (!first) + str += delim; + str += entry.str(ctx); + first = false; + } +} + +std::string IdStringList::str(const Context *ctx) const +{ + std::string s; + build_str(ctx, s); + 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/kernel/idstringlist.h b/common/kernel/idstringlist.h new file mode 100644 index 00000000..5e462d0e --- /dev/null +++ b/common/kernel/idstringlist.h @@ -0,0 +1,87 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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 IDSTRING_LIST_H +#define IDSTRING_LIST_H + +#include +#include "hashlib.h" +#include "idstring.h" +#include "nextpnr_namespaces.h" +#include "sso_array.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct Context; + +struct IdStringList +{ + SSOArray ids; + + IdStringList() : ids(1, IdString()){}; + explicit IdStringList(size_t n) : ids(n, IdString()){}; + explicit IdStringList(IdString id) : ids(1, id){}; + template explicit IdStringList(const Tlist &list) : ids(list){}; + + static IdStringList parse(Context *ctx, const std::string &str); + void build_str(const Context *ctx, std::string &str) const; + std::string str(const Context *ctx) const; + + size_t size() const { return ids.size(); } + const IdString *begin() const { return ids.begin(); } + const IdString *end() const { return ids.end(); } + const IdString &operator[](size_t idx) const { return ids[idx]; } + bool operator==(const IdStringList &other) const { return ids == other.ids; } + bool operator!=(const IdStringList &other) const { return ids != other.ids; } + bool operator<(const IdStringList &other) const + { + if (size() > other.size()) + return false; + if (size() < other.size()) + return true; + for (size_t i = 0; i < size(); i++) { + IdString a = ids[i], b = other[i]; + if (a.index < b.index) + return true; + if (a.index > b.index) + return false; + } + return false; + } + + static IdStringList concat(IdStringList a, IdStringList b); + static IdStringList concat(IdString a, IdString b) { return concat(IdStringList(a), IdStringList(b)); } + static IdStringList concat(IdStringList a, IdString b) { return concat(a, IdStringList(b)); } + static IdStringList concat(IdString a, IdStringList b) { return concat(IdStringList(a), b); } + + IdStringList slice(size_t s, size_t e) const; + + unsigned int hash() const + { + unsigned int h = mkhash_init; + for (const auto &val : ids) + h = mkhash(h, val.hash()); + return h; + } +}; + +NEXTPNR_NAMESPACE_END + +#endif /* IDSTRING_LIST_H */ diff --git a/common/kernel/indexed_store.h b/common/kernel/indexed_store.h new file mode 100644 index 00000000..df607c13 --- /dev/null +++ b/common/kernel/indexed_store.h @@ -0,0 +1,297 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021-22 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 INDEXED_STORE_H +#define INDEXED_STORE_H + +#include +#include +#include +#include + +#include "nextpnr_assertions.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +template struct store_index +{ + int32_t m_index = -1; + store_index() = default; + explicit store_index(int32_t index) : m_index(index){}; + int32_t idx() const { return m_index; } + void set(int32_t index) { m_index = index; } + bool empty() const { return m_index == -1; } + bool operator==(const store_index &other) const { return m_index == other.m_index; } + bool operator!=(const store_index &other) const { return m_index != other.m_index; } + bool operator<(const store_index &other) const { return m_index < other.m_index; } + unsigned int hash() const { return m_index; } + + operator bool() const { return !empty(); } + operator int() const = delete; + bool operator!() const { return empty(); } +}; + +// "Slotted" indexed object store +template class indexed_store +{ + private: + // This should move to using std::optional at some point + class slot + { + private: + alignas(T) unsigned char storage[sizeof(T)]; + int32_t next_free; + bool active; + inline T &obj() { return reinterpret_cast(storage); } + inline const T &obj() const { return reinterpret_cast(storage); } + friend class indexed_store; + + public: + slot() : next_free(std::numeric_limits::max()), active(false){}; + slot(slot &&other) : next_free(other.next_free), active(other.active) + { + if (active) + ::new (static_cast(&storage)) T(std::move(other.obj())); + }; + + slot(const slot &other) : next_free(other.next_free), active(other.active) + { + if (active) + ::new (static_cast(&storage)) T(other.obj()); + }; + + template void create(Args &&...args) + { + NPNR_ASSERT(!active); + active = true; + ::new (static_cast(&storage)) T(std::forward(args)...); + } + bool empty() const { return !active; } + T &get() + { + NPNR_ASSERT(active); + return reinterpret_cast(storage); + } + const T &get() const + { + NPNR_ASSERT(active); + return reinterpret_cast(storage); + } + void free(int32_t first_free) + { + NPNR_ASSERT(active); + obj().~T(); + active = false; + next_free = first_free; + } + ~slot() + { + if (active) + obj().~T(); + } + }; + + std::vector slots; + int32_t first_free = 0; + int32_t active_count = 0; + + public: + // Create a new entry and return its index + template store_index add(Args &&...args) + { + ++active_count; + if (first_free == int32_t(slots.size())) { + slots.emplace_back(); + slots.back().create(std::forward(args)...); + ++first_free; + return store_index(int32_t(slots.size()) - 1); + } else { + int32_t idx = first_free; + auto &slot = slots.at(idx); + first_free = slot.next_free; + slot.create(std::forward(args)...); + return store_index(idx); + } + } + + // Remove an entry at an index + void remove(store_index idx) + { + --active_count; + slots.at(idx.m_index).free(first_free); + first_free = idx.m_index; + } + + void clear() + { + active_count = 0; + first_free = 0; + slots.clear(); + } + + // Number of live entries + int32_t entries() const { return active_count; } + bool empty() const { return (entries() == 0); } + + // Reserve a certain amount of space + void reserve(int32_t size) { slots.reserve(size); } + + // Check if an index exists + int32_t count(store_index idx) + { + if (idx.m_index < 0 || idx.m_index >= int32_t(slots.size())) + return 0; + return slots.at(idx.m_index).empty() ? 0 : 1; + } + + // Get an item by index + T &at(store_index idx) { return slots.at(idx.m_index).get(); } + const T &at(store_index idx) const { return slots.at(idx.m_index).get(); } + T &operator[](store_index idx) { return slots.at(idx.m_index).get(); } + const T &operator[](store_index idx) const { return slots.at(idx.m_index).get(); } + + // Total size of the container + int32_t capacity() const { return int32_t(slots.size()); } + + // Iterate over items + template class enumerated_iterator; + + class iterator + { + private: + indexed_store *base; + int32_t index = 0; + + public: + iterator(indexed_store *base, int32_t index) : base(base), index(index){}; + inline bool operator!=(const iterator &other) const { return other.index != index; } + inline bool operator==(const iterator &other) const { return other.index == index; } + inline iterator operator++() + { + // skip over unused slots + do { + index++; + } while (index < int32_t(base->slots.size()) && !base->slots.at(index).active); + return *this; + } + inline iterator operator++(int) + { + iterator prior(*this); + do { + index++; + } while (index < int32_t(base->slots.size()) && !base->slots.at(index).active); + return prior; + } + T &operator*() { return base->at(store_index(index)); } + template friend class indexed_store::enumerated_iterator; + }; + iterator begin() + { + auto it = iterator{this, -1}; + ++it; + return it; + } + iterator end() { return iterator{this, int32_t(slots.size())}; } + + class const_iterator + { + private: + const indexed_store *base; + int32_t index = 0; + + public: + const_iterator(const indexed_store *base, int32_t index) : base(base), index(index){}; + inline bool operator!=(const const_iterator &other) const { return other.index != index; } + inline bool operator==(const const_iterator &other) const { return other.index == index; } + inline const_iterator operator++() + { + // skip over unused slots + do { + index++; + } while (index < int32_t(base->slots.size()) && !base->slots.at(index).active); + return *this; + } + inline const_iterator operator++(int) + { + iterator prior(*this); + do { + index++; + } while (index < int32_t(base->slots.size()) && !base->slots.at(index).active); + return prior; + } + const T &operator*() { return base->at(store_index(index)); } + template friend class indexed_store::enumerated_iterator; + }; + const_iterator begin() const + { + auto it = const_iterator{this, -1}; + ++it; + return it; + } + const_iterator end() const { return const_iterator{this, int32_t(slots.size())}; } + + template struct enumerated_item + { + enumerated_item(int32_t index, T &value) : index(index), value(value){}; + store_index> index; + S &value; + }; + + template class enumerated_iterator + { + private: + It base; + + public: + enumerated_iterator(const It &base) : base(base){}; + inline bool operator!=(const enumerated_iterator &other) const { return other.base != base; } + inline bool operator==(const enumerated_iterator &other) const { return other.base == base; } + inline enumerated_iterator operator++() + { + ++base; + return *this; + } + inline enumerated_iterator operator++(int) + { + iterator prior(*this); + ++base; + return prior; + } + enumerated_item operator*() { return enumerated_item{base.index, *base}; } + }; + + template struct enumerated_range + { + enumerated_range(const It &begin, const It &end) : m_begin(begin), m_end(end){}; + enumerated_iterator m_begin, m_end; + enumerated_iterator begin() { return m_begin; } + enumerated_iterator end() { return m_end; } + }; + + enumerated_range enumerate() { return enumerated_range{begin(), end()}; } + enumerated_range enumerate() const + { + return enumerated_range{begin(), end()}; + } +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/kernel/log.cc b/common/kernel/log.cc new file mode 100644 index 00000000..8b1ad43b --- /dev/null +++ b/common/kernel/log.cc @@ -0,0 +1,198 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "log.h" + +NEXTPNR_NAMESPACE_BEGIN + +NPNR_NORETURN void logv_error(const char *format, va_list ap) NPNR_ATTRIBUTE(noreturn); + +std::vector> log_streams; +log_write_type log_write_function = nullptr; + +std::string log_last_error; +void (*log_error_atexit)() = NULL; + +dict message_count_by_level; +static int log_newline_count = 0; +bool had_nonfatal_error = false; + +std::string stringf(const char *fmt, ...) +{ + std::string string; + va_list ap; + + va_start(ap, fmt); + string = vstringf(fmt, ap); + va_end(ap); + + return string; +} + +std::string vstringf(const char *fmt, va_list ap) +{ + std::string string; + char *str = NULL; + +#if defined(_WIN32) || defined(__CYGWIN__) + int sz = 64 + strlen(fmt), rc; + while (1) { + va_list apc; + va_copy(apc, ap); + str = (char *)realloc(str, sz); + rc = vsnprintf(str, sz, fmt, apc); + va_end(apc); + if (rc >= 0 && rc < sz) + break; + sz *= 2; + } +#else + if (vasprintf(&str, fmt, ap) < 0) + str = NULL; +#endif + + if (str != NULL) { + string = str; + free(str); + } + + return string; +} + +void logv(const char *format, va_list ap, LogLevel level = LogLevel::LOG_MSG) +{ + // + // Trim newlines from the beginning + while (format[0] == '\n' && format[1] != 0) { + log_always("\n"); + format++; + } + + std::string str = vstringf(format, ap); + + if (str.empty()) + return; + + size_t nnl_pos = str.find_last_not_of('\n'); + if (nnl_pos == std::string::npos) + log_newline_count += str.size(); + else + log_newline_count = str.size() - nnl_pos - 1; + + for (auto f : log_streams) + if (f.second <= level) + *f.first << str; + if (log_write_function) + log_write_function(str); +} + +void log_with_level(LogLevel level, const char *format, ...) +{ + message_count_by_level[level]++; + va_list ap; + va_start(ap, format); + logv(format, ap, level); + va_end(ap); +} + +void logv_prefixed(const char *prefix, const char *format, va_list ap, LogLevel level) +{ + std::string message = vstringf(format, ap); + + log_with_level(level, "%s%s", prefix, message.c_str()); + log_flush(); +} + +void log_always(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + logv(format, ap, LogLevel::ALWAYS_MSG); + va_end(ap); +} + +void log(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + logv(format, ap, LogLevel::LOG_MSG); + va_end(ap); +} + +void log_info(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + logv_prefixed("Info: ", format, ap, LogLevel::INFO_MSG); + va_end(ap); +} + +void log_warning(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + logv_prefixed("Warning: ", format, ap, LogLevel::WARNING_MSG); + va_end(ap); +} + +void log_error(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG); + + if (log_error_atexit) + log_error_atexit(); + + throw log_execution_error_exception(); +} + +void log_break() +{ + if (log_newline_count < 2) + log("\n"); + if (log_newline_count < 2) + log("\n"); +} + +void log_nonfatal_error(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG); + va_end(ap); + had_nonfatal_error = true; +} + +void log_flush() +{ + for (auto f : log_streams) + f.first->flush(); +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/log.h b/common/kernel/log.h new file mode 100644 index 00000000..0ac4edf5 --- /dev/null +++ b/common/kernel/log.h @@ -0,0 +1,92 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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 LOG_H +#define LOG_H + +#include +#include +#include +#include +#include +#include +#include +#include "hashlib.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +typedef std::function log_write_type; + +struct log_cmd_error_exception +{ +}; + +struct log_execution_error_exception +{ +}; + +enum class LogLevel +{ + LOG_MSG, + INFO_MSG, + WARNING_MSG, + ERROR_MSG, + ALWAYS_MSG +}; + +struct loglevel_hash_ops +{ + static inline bool cmp(LogLevel a, LogLevel b) { return a == b; } + static inline unsigned int hash(LogLevel a) { return unsigned(a); } +}; + +extern std::vector> log_streams; +extern log_write_type log_write_function; + +extern std::string log_last_error; +extern void (*log_error_atexit)(); +extern bool had_nonfatal_error; +extern dict message_count_by_level; + +std::string stringf(const char *fmt, ...); +std::string vstringf(const char *fmt, va_list ap); + +void log(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); +void log_always(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); +void log_info(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); +void log_warning(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); +NPNR_NORETURN void log_error(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2), noreturn); +void log_nonfatal_error(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); +void log_break(); +void log_flush(); + +static inline void log_assert_worker(bool cond, const char *expr, const char *file, int line) +{ + if (!cond) + log_error("Assert `%s' failed in %s:%d.\n", expr, file, line); +} +#define log_assert(_assert_expr_) \ + NEXTPNR_NAMESPACE_PREFIX log_assert_worker(_assert_expr_, #_assert_expr_, __FILE__, __LINE__) + +#define log_abort() log_error("Abort in %s:%d.\n", __FILE__, __LINE__) + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/kernel/nextpnr.cc b/common/kernel/nextpnr.cc new file mode 100644 index 00000000..8c902d88 --- /dev/null +++ b/common/kernel/nextpnr.cc @@ -0,0 +1,35 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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. + * + */ + +#if defined(__wasm) +#include +#include +#include "log.h" + +extern "C" { +// FIXME: WASI does not currently support exceptions. +void *__cxa_allocate_exception(size_t thrown_size) throw() { return malloc(thrown_size); } +bool __cxa_uncaught_exception() throw(); +void __cxa_throw(void *thrown_exception, struct std::type_info *tinfo, void (*dest)(void *)) { std::terminate(); } +} + +namespace boost { +void throw_exception(std::exception const &e) { NEXTPNR_NAMESPACE::log_error("boost::exception(): %s\n", e.what()); } +} // namespace boost +#endif diff --git a/common/kernel/nextpnr.h b/common/kernel/nextpnr.h new file mode 100644 index 00000000..3b65900b --- /dev/null +++ b/common/kernel/nextpnr.h @@ -0,0 +1,29 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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 +#define NEXTPNR_H + +#include "base_arch.h" +#include "context.h" +#include "nextpnr_assertions.h" +#include "nextpnr_namespaces.h" +#include "nextpnr_types.h" + +#endif diff --git a/common/kernel/nextpnr_assertions.cc b/common/kernel/nextpnr_assertions.cc new file mode 100644 index 00000000..ac4cdf57 --- /dev/null +++ b/common/kernel/nextpnr_assertions.cc @@ -0,0 +1,33 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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_assertions.h" +#include "log.h" + +NEXTPNR_NAMESPACE_BEGIN + +assertion_failure::assertion_failure(std::string msg, std::string expr_str, std::string filename, int line) + : runtime_error("Assertion failure: " + msg + " (" + filename + ":" + std::to_string(line) + ")"), msg(msg), + expr_str(expr_str), filename(filename), line(line) +{ + log_flush(); +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/nextpnr_assertions.h b/common/kernel/nextpnr_assertions.h new file mode 100644 index 00000000..1989aa3a --- /dev/null +++ b/common/kernel/nextpnr_assertions.h @@ -0,0 +1,64 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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_ASSERTIONS_H +#define NEXTPNR_ASSERTIONS_H + +#include +#include + +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +class assertion_failure : public std::runtime_error +{ + public: + assertion_failure(std::string msg, std::string expr_str, std::string filename, int line); + + std::string msg; + std::string expr_str; + std::string filename; + int line; +}; + +NPNR_NORETURN +inline void assert_fail_impl(const char *message, const char *expr_str, const char *filename, int line) +{ + throw assertion_failure(message, expr_str, filename, line); +} + +NPNR_NORETURN +inline void assert_fail_impl_str(std::string message, const char *expr_str, const char *filename, int line) +{ + throw assertion_failure(message, expr_str, filename, line); +} + +#define NPNR_ASSERT(cond) (!(cond) ? assert_fail_impl(#cond, #cond, __FILE__, __LINE__) : (void)true) +#define NPNR_ASSERT_MSG(cond, msg) (!(cond) ? assert_fail_impl(msg, #cond, __FILE__, __LINE__) : (void)true) +#define NPNR_ASSERT_FALSE(msg) (assert_fail_impl(msg, "false", __FILE__, __LINE__)) +#define NPNR_ASSERT_FALSE_STR(msg) (assert_fail_impl_str(msg, "false", __FILE__, __LINE__)) + +#define NPNR_STRINGIFY_MACRO(x) NPNR_STRINGIFY(x) +#define NPNR_STRINGIFY(x) #x + +NEXTPNR_NAMESPACE_END + +#endif /* NEXTPNR_ASSERTIONS_H */ diff --git a/common/kernel/nextpnr_base_types.h b/common/kernel/nextpnr_base_types.h new file mode 100644 index 00000000..944bf0b8 --- /dev/null +++ b/common/kernel/nextpnr_base_types.h @@ -0,0 +1,135 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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. + * + */ + +// Theses are the nextpnr types that do **not** depend on user defined types, +// like BelId, etc. +// +// If a common type is required that depends on one of the user defined types, +// add it to nextpnr_types.h, which includes "archdefs.h", or make a new +// header that includes "archdefs.h" +#ifndef NEXTPNR_BASE_TYPES_H +#define NEXTPNR_BASE_TYPES_H + +#include +#include + +#include "hashlib.h" +#include "idstring.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct GraphicElement +{ + enum type_t + { + TYPE_NONE, + TYPE_LINE, + TYPE_ARROW, + TYPE_BOX, + TYPE_CIRCLE, + TYPE_LABEL, + TYPE_LOCAL_ARROW, // Located entirely within the cell boundaries, coordinates in the range [0., 1.] + TYPE_LOCAL_LINE, + + TYPE_MAX + } type = TYPE_NONE; + + enum style_t + { + STYLE_GRID, + STYLE_FRAME, // Static "frame". Contrast between STYLE_INACTIVE and STYLE_ACTIVE + STYLE_HIDDEN, // Only display when object is selected or highlighted + STYLE_INACTIVE, // Render using low-contrast color + STYLE_ACTIVE, // Render using high-contast color + + // UI highlight groups + STYLE_HIGHLIGHTED0, + STYLE_HIGHLIGHTED1, + STYLE_HIGHLIGHTED2, + STYLE_HIGHLIGHTED3, + STYLE_HIGHLIGHTED4, + STYLE_HIGHLIGHTED5, + STYLE_HIGHLIGHTED6, + STYLE_HIGHLIGHTED7, + + STYLE_SELECTED, + STYLE_HOVER, + + STYLE_MAX + } style = STYLE_FRAME; + + float x1 = 0, y1 = 0, x2 = 0, y2 = 0, z = 0; + std::string text; + GraphicElement(){}; + GraphicElement(type_t type, style_t style, float x1, float y1, float x2, float y2, float z) + : type(type), style(style), x1(x1), y1(y1), x2(x2), y2(y2), z(z){}; +}; + +struct Loc +{ + int x = -1, y = -1, z = -1; + + Loc() {} + Loc(int x, int y, int z) : x(x), y(y), z(z) {} + + bool operator==(const Loc &other) const { return (x == other.x) && (y == other.y) && (z == other.z); } + bool operator!=(const Loc &other) const { return (x != other.x) || (y != other.y) || (z != other.z); } + unsigned int hash() const { return mkhash(x, mkhash(y, z)); } +}; + +struct ArcBounds +{ + int x0 = -1, y0 = -1, x1 = -1, y1 = -1; + + ArcBounds() {} + ArcBounds(int x0, int y0, int x1, int y1) : x0(x0), y0(y0), x1(x1), y1(y1){}; + + int distance(Loc loc) const + { + int dist = 0; + if (loc.x < x0) + dist += x0 - loc.x; + if (loc.x > x1) + dist += loc.x - x1; + if (loc.y < y0) + dist += y0 - loc.y; + if (loc.y > y1) + dist += loc.y - y1; + return dist; + }; + + bool contains(int x, int y) const { return x >= x0 && y >= y0 && x <= x1 && y <= y1; } +}; + +enum PlaceStrength +{ + STRENGTH_NONE = 0, + STRENGTH_WEAK = 1, + STRENGTH_STRONG = 2, + STRENGTH_PLACER = 3, + STRENGTH_FIXED = 4, + STRENGTH_LOCKED = 5, + STRENGTH_USER = 6 +}; + +NEXTPNR_NAMESPACE_END + +#endif /* NEXTPNR_BASE_TYPES_H */ diff --git a/common/kernel/nextpnr_namespaces.cc b/common/kernel/nextpnr_namespaces.cc new file mode 100644 index 00000000..802c89b4 --- /dev/null +++ b/common/kernel/nextpnr_namespaces.cc @@ -0,0 +1,23 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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. + * + */ + +// This cc file exists to ensure that "nextpnr_namespaces.h" can be compiled +// on its own. +#include "nextpnr_namespaces.h" diff --git a/common/kernel/nextpnr_namespaces.h b/common/kernel/nextpnr_namespaces.h new file mode 100644 index 00000000..b758d7c5 --- /dev/null +++ b/common/kernel/nextpnr_namespaces.h @@ -0,0 +1,58 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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_NAMESPACES_H +#define NEXTPNR_NAMESPACES_H + +#ifdef NEXTPNR_NAMESPACE +#define NEXTPNR_NAMESPACE_PREFIX NEXTPNR_NAMESPACE:: +#define NEXTPNR_NAMESPACE_BEGIN namespace NEXTPNR_NAMESPACE { +#define NEXTPNR_NAMESPACE_END } +#define USING_NEXTPNR_NAMESPACE using namespace NEXTPNR_NAMESPACE; +#else +#define NEXTPNR_NAMESPACE_PREFIX +#define NEXTPNR_NAMESPACE_BEGIN +#define NEXTPNR_NAMESPACE_END +#define USING_NEXTPNR_NAMESPACE +#endif + +#define NPNR_UNUSED(x) ((void)x) + +#if defined(__GNUC__) || defined(__clang__) +#define NPNR_ATTRIBUTE(...) __attribute__((__VA_ARGS__)) +#define NPNR_NORETURN __attribute__((noreturn)) +#define NPNR_DEPRECATED __attribute__((deprecated)) +#define NPNR_PACKED_STRUCT(...) __VA_ARGS__ __attribute__((packed)) +#define NPNR_ALWAYS_INLINE NPNR_ATTRIBUTE(__always_inline__) +#elif defined(_MSC_VER) +#define NPNR_ATTRIBUTE(...) +#define NPNR_NORETURN __declspec(noreturn) +#define NPNR_DEPRECATED __declspec(deprecated) +#define NPNR_PACKED_STRUCT(...) __pragma(pack(push, 1)) __VA_ARGS__ __pragma(pack(pop)) +#define NPNR_ALWAYS_INLINE +#else +#define NPNR_ATTRIBUTE(...) +#define NPNR_NORETURN +#define NPNR_DEPRECATED +#define NPNR_PACKED_STRUCT(...) __VA_ARGS__ +#define NPNR_ALWAYS_INLINE +#endif + +#endif /* NEXTPNR_NAMESPACES_H */ diff --git a/common/kernel/nextpnr_types.cc b/common/kernel/nextpnr_types.cc new file mode 100644 index 00000000..57d816c0 --- /dev/null +++ b/common/kernel/nextpnr_types.cc @@ -0,0 +1,180 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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_types.h" +#include "context.h" +#include "log.h" + +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +void CellInfo::addInput(IdString name) +{ + ports[name].name = name; + ports[name].type = PORT_IN; +} +void CellInfo::addOutput(IdString name) +{ + ports[name].name = name; + ports[name].type = PORT_OUT; +} +void CellInfo::addInout(IdString name) +{ + ports[name].name = name; + ports[name].type = PORT_INOUT; +} + +void CellInfo::setParam(IdString name, Property value) { params[name] = value; } +void CellInfo::unsetParam(IdString name) { params.erase(name); } +void CellInfo::setAttr(IdString name, Property value) { attrs[name] = value; } +void CellInfo::unsetAttr(IdString name) { attrs.erase(name); } + +bool CellInfo::testRegion(BelId bel) const +{ + return region == nullptr || !region->constr_bels || region->bels.count(bel); +} + +void CellInfo::connectPort(IdString port_name, NetInfo *net) +{ + if (net == nullptr) + return; + PortInfo &port = ports.at(port_name); + NPNR_ASSERT(port.net == nullptr); + port.net = net; + if (port.type == PORT_OUT) { + NPNR_ASSERT(net->driver.cell == nullptr); + net->driver.cell = this; + net->driver.port = port_name; + } else if (port.type == PORT_IN || port.type == PORT_INOUT) { + PortRef user; + user.cell = this; + user.port = port_name; + port.user_idx = net->users.add(user); + } else { + NPNR_ASSERT_FALSE("invalid port type for connect_port"); + } +} + +void CellInfo::disconnectPort(IdString port_name) +{ + if (!ports.count(port_name)) + return; + PortInfo &port = ports.at(port_name); + if (port.net != nullptr) { + if (port.user_idx) + port.net->users.remove(port.user_idx); + if (port.net->driver.cell == this && port.net->driver.port == port_name) + port.net->driver.cell = nullptr; + port.net = nullptr; + } +} + +void CellInfo::connectPorts(IdString port, CellInfo *other, IdString other_port) +{ + PortInfo &port1 = ports.at(port); + if (port1.net == nullptr) { + // No net on port1; need to create one + NetInfo *p1net = ctx->createNet(ctx->id(name.str(ctx) + "$conn$" + port.str(ctx))); + connectPort(port, p1net); + } + other->connectPort(other_port, port1.net); +} + +void CellInfo::movePortTo(IdString port, CellInfo *other, IdString other_port) +{ + if (!ports.count(port)) + return; + PortInfo &old = ports.at(port); + + // Create port on the replacement cell if it doesn't already exist + if (!other->ports.count(other_port)) { + other->ports[other_port].name = other_port; + other->ports[other_port].type = old.type; + } + + PortInfo &rep = other->ports.at(other_port); + NPNR_ASSERT(old.type == rep.type); + + rep.net = old.net; + rep.user_idx = old.user_idx; + old.net = nullptr; + old.user_idx = store_index{}; + if (rep.type == PORT_OUT) { + if (rep.net != nullptr) { + rep.net->driver.cell = other; + rep.net->driver.port = other_port; + } + } else if (rep.type == PORT_IN) { + if (rep.net != nullptr) { + auto &load = rep.net->users.at(rep.user_idx); + load.cell = other; + load.port = other_port; + } + } else { + NPNR_ASSERT(false); + } +} + +void CellInfo::renamePort(IdString old_name, IdString new_name) +{ + if (!ports.count(old_name)) + return; + PortInfo pi = ports.at(old_name); + if (pi.net != nullptr) { + if (pi.net->driver.cell == this && pi.net->driver.port == old_name) + pi.net->driver.port = new_name; + if (pi.user_idx) + pi.net->users.at(pi.user_idx).port = new_name; + } + ports.erase(old_name); + pi.name = new_name; + ports[new_name] = pi; +} + +void CellInfo::movePortBusTo(IdString old_name, int old_offset, bool old_brackets, CellInfo *new_cell, + IdString new_name, int new_offset, bool new_brackets, int width) +{ + for (int i = 0; i < width; i++) { + IdString old_port = ctx->id(stringf(old_brackets ? "%s[%d]" : "%s%d", old_name.c_str(ctx), i + old_offset)); + IdString new_port = ctx->id(stringf(new_brackets ? "%s[%d]" : "%s%d", new_name.c_str(ctx), i + new_offset)); + movePortTo(old_port, new_cell, new_port); + } +} + +void CellInfo::copyPortTo(IdString port, CellInfo *other, IdString other_port) +{ + if (!ports.count(port)) + return; + other->ports[other_port].name = other_port; + other->ports[other_port].type = ports.at(port).type; + other->connectPort(other_port, ports.at(port).net); +} + +void CellInfo::copyPortBusTo(IdString old_name, int old_offset, bool old_brackets, CellInfo *new_cell, + IdString new_name, int new_offset, bool new_brackets, int width) +{ + for (int i = 0; i < width; i++) { + IdString old_port = ctx->id(stringf(old_brackets ? "%s[%d]" : "%s%d", old_name.c_str(ctx), i + old_offset)); + IdString new_port = ctx->id(stringf(new_brackets ? "%s[%d]" : "%s%d", new_name.c_str(ctx), i + new_offset)); + copyPortTo(old_port, new_cell, new_port); + } +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/nextpnr_types.h b/common/kernel/nextpnr_types.h new file mode 100644 index 00000000..c21182cc --- /dev/null +++ b/common/kernel/nextpnr_types.h @@ -0,0 +1,364 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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. + * + */ + +// Types defined in this header use one or more user defined types (e.g. BelId). +// If a new common type is desired that doesn't depend on a user defined type, +// either put it in it's own header, or in nextpnr_base_types.h. +#ifndef NEXTPNR_TYPES_H +#define NEXTPNR_TYPES_H + +#include +#include + +#include "archdefs.h" +#include "hashlib.h" +#include "indexed_store.h" +#include "nextpnr_base_types.h" +#include "nextpnr_namespaces.h" +#include "property.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct DecalXY +{ + DecalId decal; + float x = 0, y = 0; + + bool operator==(const DecalXY &other) const { return (decal == other.decal && x == other.x && y == other.y); } +}; + +struct BelPin +{ + BelId bel; + IdString pin; +}; + +struct Region +{ + IdString name; + + bool constr_bels = false; + bool constr_wires = false; + bool constr_pips = false; + + pool bels; + pool wires; + pool piplocs; +}; + +struct PipMap +{ + PipId pip = PipId(); + PlaceStrength strength = STRENGTH_NONE; +}; + +struct CellInfo; + +struct PortRef +{ + CellInfo *cell = nullptr; + IdString port; + delay_t budget = 0; +}; + +// minimum and maximum delay +struct DelayPair +{ + DelayPair(){}; + explicit DelayPair(delay_t delay) : min_delay(delay), max_delay(delay){}; + DelayPair(delay_t min_delay, delay_t max_delay) : min_delay(min_delay), max_delay(max_delay){}; + delay_t minDelay() const { return min_delay; }; + delay_t maxDelay() const { return max_delay; }; + delay_t min_delay, max_delay; + DelayPair operator+(const DelayPair &other) const + { + return {min_delay + other.min_delay, max_delay + other.max_delay}; + } + DelayPair operator-(const DelayPair &other) const + { + return {min_delay - other.min_delay, max_delay - other.max_delay}; + } +}; + +// four-quadrant, min and max rise and fall delay +struct DelayQuad +{ + DelayPair rise, fall; + DelayQuad(){}; + explicit DelayQuad(delay_t delay) : rise(delay), fall(delay){}; + DelayQuad(delay_t min_delay, delay_t max_delay) : rise(min_delay, max_delay), fall(min_delay, max_delay){}; + DelayQuad(DelayPair rise, DelayPair fall) : rise(rise), fall(fall){}; + DelayQuad(delay_t min_rise, delay_t max_rise, delay_t min_fall, delay_t max_fall) + : rise(min_rise, max_rise), fall(min_fall, max_fall){}; + + delay_t minRiseDelay() const { return rise.minDelay(); }; + delay_t maxRiseDelay() const { return rise.maxDelay(); }; + delay_t minFallDelay() const { return fall.minDelay(); }; + delay_t maxFallDelay() const { return fall.maxDelay(); }; + delay_t minDelay() const { return std::min(rise.minDelay(), fall.minDelay()); }; + delay_t maxDelay() const { return std::max(rise.maxDelay(), fall.maxDelay()); }; + + DelayPair delayPair() const { return DelayPair(minDelay(), maxDelay()); }; + + DelayQuad operator+(const DelayQuad &other) const { return {rise + other.rise, fall + other.fall}; } + DelayQuad operator-(const DelayQuad &other) const { return {rise - other.rise, fall - other.fall}; } +}; + +struct ClockConstraint; + +struct NetInfo : ArchNetInfo +{ + explicit NetInfo(IdString name) : name(name){}; + IdString name, hierpath; + int32_t udata = 0; + + PortRef driver; + indexed_store users; + dict attrs; + + // wire -> uphill_pip + dict wires; + + std::vector aliases; // entries in net_aliases that point to this net + + std::unique_ptr clkconstr; + + Region *region = nullptr; +}; + +enum PortType +{ + PORT_IN = 0, + PORT_OUT = 1, + PORT_INOUT = 2 +}; + +struct PortInfo +{ + IdString name; + NetInfo *net; + PortType type; + store_index user_idx{}; +}; + +struct Context; + +struct CellInfo : ArchCellInfo +{ + CellInfo(Context *ctx, IdString name, IdString type) : ctx(ctx), name(name), type(type){}; + Context *ctx = nullptr; + + IdString name, type, hierpath; + int32_t udata; + + dict ports; + dict attrs, params; + + BelId bel; + PlaceStrength belStrength = STRENGTH_NONE; + + // cell is part of a cluster if != ClusterId + ClusterId cluster; + + Region *region = nullptr; + + void addInput(IdString name); + void addOutput(IdString name); + void addInout(IdString name); + + void setParam(IdString name, Property value); + void unsetParam(IdString name); + void setAttr(IdString name, Property value); + void unsetAttr(IdString name); + // check whether a bel complies with the cell's region constraint + bool testRegion(BelId bel) const; + + NetInfo *getPort(IdString name) + { + auto found = ports.find(name); + return (found == ports.end()) ? nullptr : found->second.net; + } + const NetInfo *getPort(IdString name) const + { + auto found = ports.find(name); + return (found == ports.end()) ? nullptr : found->second.net; + } + void connectPort(IdString port, NetInfo *net); + void disconnectPort(IdString port); + void connectPorts(IdString port, CellInfo *other, IdString other_port); + void movePortTo(IdString port, CellInfo *other, IdString other_port); + void renamePort(IdString old_name, IdString new_name); + void movePortBusTo(IdString old_name, int old_offset, bool old_brackets, CellInfo *new_cell, IdString new_name, + int new_offset, bool new_brackets, int width); + void copyPortTo(IdString port, CellInfo *other, IdString other_port); + void copyPortBusTo(IdString old_name, int old_offset, bool old_brackets, CellInfo *new_cell, IdString new_name, + int new_offset, bool new_brackets, int width); +}; + +enum TimingPortClass +{ + TMG_CLOCK_INPUT, // Clock input to a sequential cell + TMG_GEN_CLOCK, // Generated clock output (PLL, DCC, etc) + TMG_REGISTER_INPUT, // Input to a register, with an associated clock (may also have comb. fanout too) + TMG_REGISTER_OUTPUT, // Output from a register + TMG_COMB_INPUT, // Combinational input, no paths end here + TMG_COMB_OUTPUT, // Combinational output, no paths start here + TMG_STARTPOINT, // Unclocked primary startpoint, such as an IO cell output + TMG_ENDPOINT, // Unclocked primary endpoint, such as an IO cell input + TMG_IGNORE, // Asynchronous to all clocks, "don't care", and should be ignored (false path) for analysis +}; + +enum ClockEdge +{ + RISING_EDGE, + FALLING_EDGE +}; + +struct TimingClockingInfo +{ + IdString clock_port; // Port name of clock domain + ClockEdge edge; + DelayPair setup, hold; // Input timing checks + DelayQuad clockToQ; // Output clock-to-Q time +}; + +struct ClockConstraint +{ + DelayPair high; + DelayPair low; + DelayPair period; +}; + +struct ClockFmax +{ + float achieved; + float constraint; +}; + +struct ClockEvent +{ + IdString clock; + ClockEdge edge; + + bool operator==(const ClockEvent &other) const { return clock == other.clock && edge == other.edge; } + unsigned int hash() const { return mkhash(clock.hash(), int(edge)); } +}; + +struct ClockPair +{ + ClockEvent start, end; + + bool operator==(const ClockPair &other) const { return start == other.start && end == other.end; } + unsigned int hash() const { return mkhash(start.hash(), end.hash()); } +}; + +struct CriticalPath +{ + struct Segment + { + + // Segment type + enum class Type + { + CLK_TO_Q, // Clock-to-Q delay + SOURCE, // Delayless source + LOGIC, // Combinational logic delay + ROUTING, // Routing delay + SETUP // Setup time in sink + }; + + // Type + Type type; + // Net name (routing only) + IdString net; + // From cell.port + std::pair from; + // To cell.port + std::pair to; + // Segment delay + delay_t delay; + // Segment budget (routing only) + delay_t budget; + }; + + // Clock pair + ClockPair clock_pair; + // Total path delay + delay_t delay; + // Period (max allowed delay) + delay_t period; + // Individual path segments + std::vector segments; +}; + +// Holds timing information of a single source to sink path of a net +struct NetSinkTiming +{ + // Clock event pair + ClockPair clock_pair; + // Cell and port (the sink) + std::pair cell_port; + // Delay + delay_t delay; + // Delay budget + delay_t budget; +}; + +struct TimingResult +{ + // Achieved and target Fmax for all clock domains + dict clock_fmax; + // Single domain critical paths + dict clock_paths; + // Cross-domain critical paths + std::vector xclock_paths; + + // Detailed net timing data + dict> detailed_net_timings; +}; + +// Represents the contents of a non-leaf cell in a design +// with hierarchy + +struct HierarchicalPort +{ + IdString name; + PortType dir; + std::vector nets; + int offset; + bool upto; +}; + +struct HierarchicalCell +{ + IdString name, type, parent, fullpath; + // Name inside cell instance -> global name + dict leaf_cells, nets; + // Global name -> name inside cell instance + dict leaf_cells_by_gname, nets_by_gname; + // Cell port to net + dict ports; + // Name inside cell instance -> global name + dict hier_cells; +}; + +NEXTPNR_NAMESPACE_END + +#endif /* NEXTPNR_TYPES_H */ diff --git a/common/kernel/property.cc b/common/kernel/property.cc new file mode 100644 index 00000000..6c30436d --- /dev/null +++ b/common/kernel/property.cc @@ -0,0 +1,80 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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 "property.h" + +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +Property::Property() : is_string(false), str(""), intval(0) {} + +Property::Property(int64_t intval, int width) : is_string(false), intval(intval) +{ + str.reserve(width); + for (int i = 0; i < width; i++) + str.push_back((intval & (1ULL << i)) ? S1 : S0); +} + +Property::Property(const std::string &strval) : is_string(true), str(strval), intval(0xDEADBEEF) {} + +Property::Property(State bit) : is_string(false), str(std::string("") + char(bit)), intval(bit == S1) {} + +std::string Property::to_string() const +{ + if (is_string) { + std::string result = str; + int state = 0; + for (char c : str) { + if (state == 0) { + if (c == '0' || c == '1' || c == 'x' || c == 'z') + state = 0; + else if (c == ' ') + state = 1; + else + state = 2; + } else if (state == 1 && c != ' ') + state = 2; + } + if (state < 2) + result += " "; + return result; + } else { + return std::string(str.rbegin(), str.rend()); + } +} + +Property Property::from_string(const std::string &s) +{ + Property p; + + size_t cursor = s.find_first_not_of("01xz"); + if (cursor == std::string::npos) { + p.str = std::string(s.rbegin(), s.rend()); + p.is_string = false; + p.update_intval(); + } else if (s.find_first_not_of(' ', cursor) == std::string::npos) { + p = Property(s.substr(0, s.size() - 1)); + } else { + p = Property(s); + } + return p; +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/property.h b/common/kernel/property.h new file mode 100644 index 00000000..814b2cac --- /dev/null +++ b/common/kernel/property.h @@ -0,0 +1,131 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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 PROPERTY_H +#define PROPERTY_H + +#include +#include +#include +#include + +#include "nextpnr_assertions.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct Property +{ + enum State : char + { + S0 = '0', + S1 = '1', + Sx = 'x', + Sz = 'z' + }; + + Property(); + Property(int64_t intval, int width = 32); + Property(const std::string &strval); + Property(State bit); + Property &operator=(const Property &other) = default; + + bool is_string; + + // The string literal (for string values), or a string of [01xz] (for numeric values) + std::string str; + // The lower 64 bits (for numeric values), unused for string values + int64_t intval; + + void update_intval() + { + intval = 0; + for (int i = 0; i < int(str.size()); i++) { + NPNR_ASSERT(str[i] == S0 || str[i] == S1 || str[i] == Sx || str[i] == Sz); + if ((str[i] == S1) && i < 64) + intval |= (1ULL << i); + } + } + + int64_t as_int64() const + { + NPNR_ASSERT(!is_string); + return intval; + } + std::vector as_bits() const + { + std::vector result; + result.reserve(str.size()); + NPNR_ASSERT(!is_string); + for (auto c : str) + result.push_back(c == S1); + return result; + } + const std::string &as_string() const + { + NPNR_ASSERT(is_string); + return str; + } + const char *c_str() const + { + NPNR_ASSERT(is_string); + return str.c_str(); + } + size_t size() const { return is_string ? 8 * str.size() : str.size(); } + double as_double() const + { + NPNR_ASSERT(is_string); + return std::stod(str); + } + bool as_bool() const + { + if (int(str.size()) <= 64) + return intval != 0; + else + return std::any_of(str.begin(), str.end(), [](char c) { return c == S1; }); + } + bool is_fully_def() const + { + return !is_string && std::all_of(str.begin(), str.end(), [](char c) { return c == S0 || c == S1; }); + } + Property extract(int offset, int len, State padding = State::S0) const + { + Property ret; + ret.is_string = false; + ret.str.reserve(len); + for (int i = offset; i < offset + len; i++) + ret.str.push_back(i < int(str.size()) ? str[i] : char(padding)); + ret.update_intval(); + return ret; + } + // Convert to a string representation, escaping literal strings matching /^[01xz]* *$/ by adding a space at the end, + // to disambiguate from binary strings + std::string to_string() const; + // Convert a string of four-value binary [01xz], or a literal string escaped according to the above rule + // to a Property + static Property from_string(const std::string &s); +}; + +inline bool operator==(const Property &a, const Property &b) { return a.is_string == b.is_string && a.str == b.str; } +inline bool operator!=(const Property &a, const Property &b) { return a.is_string != b.is_string || a.str != b.str; } + +NEXTPNR_NAMESPACE_END + +#endif /* PROPERTY_H */ diff --git a/common/kernel/pybindings.cc b/common/kernel/pybindings.cc new file mode 100644 index 00000000..9a783eb4 --- /dev/null +++ b/common/kernel/pybindings.cc @@ -0,0 +1,362 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * 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 "pybindings.h" +#include "arch_pybindings.h" +#include "json_frontend.h" +#include "log.h" +#include "nextpnr.h" + +#include +#include +#include +NEXTPNR_NAMESPACE_BEGIN + +// Required to determine concatenated module name (which differs for different +// archs) +#define PASTER(x, y) x##_##y +#define EVALUATOR(x, y) PASTER(x, y) +#define MODULE_NAME EVALUATOR(nextpnrpy, ARCHNAME) +#define PYINIT_MODULE_NAME EVALUATOR(&PyInit_nextpnrpy, ARCHNAME) +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +// Architecture-specific bindings should be created in the below function, which +// must be implemented in all architectures +void arch_wrap_python(py::module &m); + +bool operator==(const PortRef &a, const PortRef &b) { return (a.cell == b.cell) && (a.port == b.port); } + +// Load a JSON file into a design +void parse_json_shim(std::string filename, Context &d) +{ + std::ifstream inf(filename); + if (!inf) + throw std::runtime_error("failed to open file " + filename); + parse_json(inf, filename, &d); +} + +// Create a new Chip and load design from json file +Context *load_design_shim(std::string filename, ArchArgs args) +{ + Context *d = new Context(args); + parse_json_shim(filename, *d); + return d; +} + +namespace PythonConversion { +template <> struct string_converter +{ + inline PortRef from_str(Context *ctx, std::string name) { NPNR_ASSERT_FALSE("PortRef from_str not implemented"); } + + inline std::string to_str(Context *ctx, const PortRef &pr) + { + return pr.cell->name.str(ctx) + "." + pr.port.str(ctx); + } +}; + +template <> struct string_converter +{ + inline Property from_str(Context *ctx, std::string s) { return Property::from_string(s); } + + inline std::string to_str(Context *ctx, Property p) { return p.to_string(); } +}; + +} // namespace PythonConversion + +std::string loc_repr_py(Loc loc) { return stringf("Loc(%d, %d, %d)", loc.x, loc.y, loc.z); } + +PYBIND11_EMBEDDED_MODULE(MODULE_NAME, m) +{ + py::register_exception_translator([](std::exception_ptr p) { + try { + if (p) + std::rethrow_exception(p); + } catch (const assertion_failure &e) { + PyErr_SetString(PyExc_AssertionError, e.what()); + } + }); + + using namespace PythonConversion; + + py::enum_(m, "GraphicElementType") + .value("TYPE_NONE", GraphicElement::TYPE_NONE) + .value("TYPE_LINE", GraphicElement::TYPE_LINE) + .value("TYPE_ARROW", GraphicElement::TYPE_ARROW) + .value("TYPE_BOX", GraphicElement::TYPE_BOX) + .value("TYPE_CIRCLE", GraphicElement::TYPE_CIRCLE) + .value("TYPE_LABEL", GraphicElement::TYPE_LABEL) + .export_values(); + + py::enum_(m, "GraphicElementStyle") + .value("STYLE_GRID", GraphicElement::STYLE_GRID) + .value("STYLE_FRAME", GraphicElement::STYLE_FRAME) + .value("STYLE_HIDDEN", GraphicElement::STYLE_HIDDEN) + .value("STYLE_INACTIVE", GraphicElement::STYLE_INACTIVE) + .value("STYLE_ACTIVE", GraphicElement::STYLE_ACTIVE) + .export_values(); + + py::class_(m, "GraphicElement") + .def(py::init(), + py::arg("type"), py::arg("style"), py::arg("x1"), py::arg("y1"), py::arg("x2"), py::arg("y2"), + py::arg("z")) + .def_readwrite("type", &GraphicElement::type) + .def_readwrite("x1", &GraphicElement::x1) + .def_readwrite("y1", &GraphicElement::y1) + .def_readwrite("x2", &GraphicElement::x2) + .def_readwrite("y2", &GraphicElement::y2) + .def_readwrite("text", &GraphicElement::text); + + py::enum_(m, "PortType") + .value("PORT_IN", PORT_IN) + .value("PORT_OUT", PORT_OUT) + .value("PORT_INOUT", PORT_INOUT) + .export_values(); + + py::enum_(m, "PlaceStrength") + .value("STRENGTH_NONE", STRENGTH_NONE) + .value("STRENGTH_WEAK", STRENGTH_WEAK) + .value("STRENGTH_STRONG", STRENGTH_STRONG) + .value("STRENGTH_FIXED", STRENGTH_FIXED) + .value("STRENGTH_LOCKED", STRENGTH_LOCKED) + .value("STRENGTH_USER", STRENGTH_USER) + .export_values(); + + py::class_(m, "DelayPair") + .def(py::init<>()) + .def(py::init()) + .def(py::init()) + .def_readwrite("min_delay", &DelayPair::min_delay) + .def_readwrite("max_delay", &DelayPair::max_delay) + .def("minDelay", &DelayPair::minDelay) + .def("maxDelay", &DelayPair::maxDelay); + + py::class_(m, "DelayQuad") + .def(py::init<>()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def_readwrite("rise", &DelayQuad::rise) + .def_readwrite("fall", &DelayQuad::fall) + .def("minDelay", &DelayQuad::minDelay) + .def("minRiseDelay", &DelayQuad::minRiseDelay) + .def("minFallDelay", &DelayQuad::minFallDelay) + .def("maxDelay", &DelayQuad::maxDelay) + .def("maxRiseDelay", &DelayQuad::maxRiseDelay) + .def("maxFallDelay", &DelayQuad::maxFallDelay) + .def("delayPair", &DelayQuad::delayPair); + + typedef dict AttrMap; + typedef dict PortMap; + typedef dict IdIdMap; + typedef dict> RegionMap; + + py::class_(m, "BaseCtx"); + + auto loc_cls = py::class_(m, "Loc") + .def(py::init()) + .def_readwrite("x", &Loc::x) + .def_readwrite("y", &Loc::y) + .def_readwrite("z", &Loc::z) + .def("__repr__", loc_repr_py); + + auto ci_cls = py::class_>(m, "CellInfo"); + readwrite_wrapper, + conv_from_str>::def_wrap(ci_cls, "name"); + readwrite_wrapper, + conv_from_str>::def_wrap(ci_cls, "type"); + readonly_wrapper>::def_wrap( + ci_cls, "attrs"); + readonly_wrapper>::def_wrap( + ci_cls, "params"); + readonly_wrapper>::def_wrap( + ci_cls, "ports"); + readwrite_wrapper, + conv_from_str>::def_wrap(ci_cls, "bel"); + readwrite_wrapper, + pass_through>::def_wrap(ci_cls, "belStrength"); + + fn_wrapper_1a_v>::def_wrap( + ci_cls, "addInput"); + fn_wrapper_1a_v>::def_wrap(ci_cls, "addOutput"); + fn_wrapper_1a_v>::def_wrap( + ci_cls, "addInout"); + + fn_wrapper_2a_v, + conv_from_str>::def_wrap(ci_cls, "setParam"); + fn_wrapper_1a_v>::def_wrap(ci_cls, "unsetParam"); + fn_wrapper_2a_v, + conv_from_str>::def_wrap(ci_cls, "setAttr"); + fn_wrapper_1a_v>::def_wrap(ci_cls, "unsetAttr"); + + auto pi_cls = py::class_>(m, "PortInfo"); + readwrite_wrapper, + conv_from_str>::def_wrap(pi_cls, "name"); + readonly_wrapper>::def_wrap(pi_cls, + "net"); + readwrite_wrapper, + pass_through>::def_wrap(pi_cls, "type"); + + typedef indexed_store PortRefVector; + typedef dict WireMap; + typedef pool BelSet; + typedef pool WireSet; + + auto ni_cls = py::class_>(m, "NetInfo"); + readwrite_wrapper, + conv_from_str>::def_wrap(ni_cls, "name"); + readonly_wrapper>::def_wrap( + ni_cls, "driver"); + readonly_wrapper>::def_wrap( + ni_cls, "users"); + readonly_wrapper>::def_wrap(ni_cls, + "wires"); + + auto pr_cls = py::class_>(m, "PortRef"); + readonly_wrapper>::def_wrap(pr_cls, + "cell"); + readonly_wrapper>::def_wrap(pr_cls, + "port"); + readonly_wrapper>::def_wrap(pr_cls, + "budget"); + + auto pm_cls = py::class_>(m, "PipMap"); + readwrite_wrapper, + conv_from_str>::def_wrap(pm_cls, "pip"); + readwrite_wrapper, + pass_through>::def_wrap(pm_cls, "strength"); + + m.def("parse_json", parse_json_shim); + m.def("load_design", load_design_shim, py::return_value_policy::take_ownership); + + auto region_cls = py::class_>(m, "Region"); + readwrite_wrapper, + conv_from_str>::def_wrap(region_cls, "name"); + readwrite_wrapper, + pass_through>::def_wrap(region_cls, "constr_bels"); + readwrite_wrapper, + pass_through>::def_wrap(region_cls, "constr_bels"); + readwrite_wrapper, + pass_through>::def_wrap(region_cls, "constr_pips"); + readonly_wrapper>::def_wrap(region_cls, + "bels"); + readonly_wrapper>::def_wrap(region_cls, + "wires"); + + auto hierarchy_cls = py::class_>(m, "HierarchicalCell"); + readwrite_wrapper, conv_from_str>::def_wrap(hierarchy_cls, "name"); + readwrite_wrapper, conv_from_str>::def_wrap(hierarchy_cls, "type"); + readwrite_wrapper, conv_from_str>::def_wrap(hierarchy_cls, "parent"); + readwrite_wrapper, conv_from_str>::def_wrap(hierarchy_cls, "fullpath"); + + readonly_wrapper>::def_wrap(hierarchy_cls, "leaf_cells"); + readonly_wrapper>::def_wrap(hierarchy_cls, "nets"); + readonly_wrapper>::def_wrap(hierarchy_cls, "hier_cells"); + WRAP_MAP(m, AttrMap, conv_to_str, "AttrMap"); + WRAP_MAP(m, PortMap, wrap_context, "PortMap"); + WRAP_MAP(m, IdIdMap, conv_to_str, "IdIdMap"); + WRAP_MAP(m, WireMap, wrap_context, "WireMap"); + WRAP_MAP_UPTR(m, RegionMap, "RegionMap"); + + WRAP_INDEXSTORE(m, PortRefVector, wrap_context); + + typedef dict ClockFmaxMap; + WRAP_MAP(m, ClockFmaxMap, pass_through, "ClockFmaxMap"); + + auto clk_fmax_cls = py::class_(m, "ClockFmax") + .def_readonly("achieved", &ClockFmax::achieved) + .def_readonly("constraint", &ClockFmax::constraint); + + auto tmg_result_cls = py::class_>(m, "TimingResult"); + readonly_wrapper>::def_wrap(tmg_result_cls, "clock_fmax"); + arch_wrap_python(m); +} + +#ifdef MAIN_EXECUTABLE +static wchar_t *program; +#endif + +void (*python_sighandler)(int) = nullptr; + +void init_python(const char *executable) +{ +#ifdef MAIN_EXECUTABLE + program = Py_DecodeLocale(executable, NULL); + if (program == NULL) { + fprintf(stderr, "Fatal error: cannot decode executable filename\n"); + exit(1); + } + Py_SetProgramName(program); + py::initialize_interpreter(); + py::module::import(TOSTRING(MODULE_NAME)); + PyRun_SimpleString("from " TOSTRING(MODULE_NAME) " import *"); + python_sighandler = signal(SIGINT, SIG_DFL); +#endif +} + +void deinit_python() +{ +#ifdef MAIN_EXECUTABLE + py::finalize_interpreter(); + PyMem_RawFree(program); +#endif +} + +void execute_python_file(const char *python_file) +{ + try { + FILE *fp = fopen(python_file, "r"); + if (fp == NULL) { + fprintf(stderr, "Fatal error: file not found %s\n", python_file); + exit(1); + } + if (python_sighandler) + signal(SIGINT, python_sighandler); + int result = PyRun_SimpleFile(fp, python_file); + signal(SIGINT, SIG_DFL); + fclose(fp); + if (result == -1) { + log_error("Error occurred while executing Python script %s\n", python_file); + } + } catch (py::error_already_set const &) { + // Parse and output the exception + std::string perror_str = parse_python_exception(); + signal(SIGINT, SIG_DFL); + log_error("Error in Python: %s\n", perror_str.c_str()); + } +} + +NEXTPNR_NAMESPACE_END + +#endif // NO_PYTHON diff --git a/common/kernel/pybindings.h b/common/kernel/pybindings.h new file mode 100644 index 00000000..695441f3 --- /dev/null +++ b/common/kernel/pybindings.h @@ -0,0 +1,93 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * 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 COMMON_PYBINDINGS_H +#define COMMON_PYBINDINGS_H + +#include +#include +#include +#include +#include +#include +#include "pycontainers.h" +#include "pywrappers.h" + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace py = pybind11; + +std::string parse_python_exception(); + +template void python_export_global(const char *name, Tn &x) +{ + try { + py::object obj = py::cast(x, py::return_value_policy::reference); + py::module::import("__main__").attr(name) = obj.ptr(); + } catch (pybind11::error_already_set &) { + // Parse and output the exception + std::string perror_str = parse_python_exception(); + std::cout << "Error in Python: " << perror_str << std::endl; + std::terminate(); + } +}; + +void init_python(const char *executable); + +void deinit_python(); + +void execute_python_file(const char *python_file); + +// Defauld IdString conversions +namespace PythonConversion { + +template <> struct string_converter +{ + inline IdString from_str(Context *ctx, std::string name) { return ctx->id(name); } + + inline std::string to_str(Context *ctx, IdString id) { return id.str(ctx); } +}; + +template <> struct string_converter +{ + inline IdString from_str(Context *ctx, std::string name) { return ctx->id(name); } + + inline std::string to_str(Context *ctx, IdString id) { return id.str(ctx); } +}; + +template <> struct string_converter +{ + IdStringList from_str(Context *ctx, std::string name) { return IdStringList::parse(ctx, name); } + std::string to_str(Context *ctx, const IdStringList &id) { return id.str(ctx); } +}; + +template <> struct string_converter +{ + IdStringList from_str(Context *ctx, std::string name) { return IdStringList::parse(ctx, name); } + std::string to_str(Context *ctx, const IdStringList &id) { return id.str(ctx); } +}; + +} // namespace PythonConversion + +NEXTPNR_NAMESPACE_END + +#endif /* end of include guard: COMMON_PYBINDINGS_HH */ diff --git a/common/kernel/pycontainers.h b/common/kernel/pycontainers.h new file mode 100644 index 00000000..ff49c34c --- /dev/null +++ b/common/kernel/pycontainers.h @@ -0,0 +1,575 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * 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 COMMON_PYCONTAINERS_H +#define COMMON_PYCONTAINERS_H + +#include +#include +#include +#include +#include +#include "nextpnr.h" +#include "pywrappers.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace py = pybind11; + +inline void KeyError() +{ + PyErr_SetString(PyExc_KeyError, "Key not found"); + throw py::error_already_set(); +} + +/* +A wrapper for a Pythonised nextpnr Iterator. The actual class wrapped is a +pair containing (current, end), wrapped in a ContextualWrapper + +*/ + +template > +struct iterator_wrapper +{ + typedef decltype(*(std::declval())) value_t; + + typedef PythonConversion::ContextualWrapper> wrapped_iter_t; + using return_t = typename value_conv::ret_type; + + static return_t next(wrapped_iter_t &iter) + { + if (iter.base.first != iter.base.second) { + return_t val = value_conv()(iter.ctx, *iter.base.first); + ++iter.base.first; + return val; + } else { + PyErr_SetString(PyExc_StopIteration, "End of range reached"); + throw py::error_already_set(); + } + } + + static void wrap(py::module &m, const char *python_name) + { + py::class_(m, python_name).def("__next__", next, P); + } +}; + +/* +A pair that doesn't automatically become a tuple +*/ +template struct iter_pair +{ + iter_pair(){}; + iter_pair(const Ta &first, const Tb &second) : first(first), second(second){}; + Ta first; + Tb second; +}; + +/* +A wrapper for a nextpnr Range. Ranges should have two functions, begin() +and end() which return iterator-like objects supporting ++, * and != +Full STL iterator semantics are not required, unlike the standard Boost wrappers +*/ + +template > +struct range_wrapper +{ + typedef decltype(std::declval().begin()) iterator_t; + typedef decltype(*(std::declval())) value_t; + typedef typename PythonConversion::ContextualWrapper wrapped_range; + typedef typename PythonConversion::ContextualWrapper> wrapped_pair; + static wrapped_pair iter(wrapped_range &range) + { + return wrapped_pair(range.ctx, std::make_pair(range.base.begin(), range.base.end())); + } + + static std::string repr(wrapped_range &range) + { + PythonConversion::string_converter conv; + bool first = true; + std::stringstream ss; + ss << "["; + for (const auto &item : range.base) { + if (!first) + ss << ", "; + ss << "'" << conv.to_str(range.ctx, item) << "'"; + first = false; + } + ss << "]"; + return ss.str(); + } + + static void wrap(py::module &m, const char *range_name, const char *iter_name) + { + py::class_(m, range_name).def("__iter__", iter).def("__repr__", repr); + iterator_wrapper().wrap(m, iter_name); + } + + typedef iterator_wrapper iter_wrap; +}; + +#define WRAP_RANGE(m, t, conv) \ + range_wrapper().wrap(m, #t "Range", #t "Iterator") + +/* +A wrapper for a vector or similar structure. With support for conversion +*/ + +template > +struct vector_wrapper +{ + typedef decltype(std::declval().begin()) iterator_t; + typedef decltype(*(std::declval())) value_t; + typedef typename PythonConversion::ContextualWrapper wrapped_vector; + typedef typename PythonConversion::ContextualWrapper> wrapped_pair; + using return_t = typename value_conv::ret_type; + static wrapped_pair iter(wrapped_vector &range) + { + return wrapped_pair(range.ctx, std::make_pair(range.base.begin(), range.base.end())); + } + + static std::string repr(wrapped_vector &range) + { + PythonConversion::string_converter conv; + bool first = true; + std::stringstream ss; + ss << "["; + for (const auto &item : range.base) { + if (!first) + ss << ", "; + ss << "'" << conv.to_str(range.ctx, item) << "'"; + first = false; + } + ss << "]"; + return ss.str(); + } + + static int len(wrapped_vector &range) { return range.base.size(); } + + static return_t getitem(wrapped_vector &range, int i) + { + return value_conv()(range.ctx, boost::ref(range.base.at(i))); + } + + static void wrap(py::module &m, const char *range_name, const char *iter_name) + { + py::class_(m, range_name) + .def("__iter__", iter) + .def("__repr__", repr) + .def("__len__", len) + .def("__getitem__", getitem); + + iterator_wrapper().wrap(m, iter_name); + } + + typedef iterator_wrapper iter_wrap; +}; + +#define WRAP_VECTOR(m, t, conv) vector_wrapper().wrap(m, #t, #t "Iterator") + +template > +struct indexed_store_wrapper +{ + typedef decltype(std::declval().begin()) iterator_t; + typedef decltype(*(std::declval())) value_t; + typedef typename PythonConversion::ContextualWrapper wrapped_vector; + typedef typename PythonConversion::ContextualWrapper> wrapped_pair; + using return_t = typename value_conv::ret_type; + static wrapped_pair iter(wrapped_vector &range) + { + return wrapped_pair(range.ctx, std::make_pair(range.base.begin(), range.base.end())); + } + + static std::string repr(wrapped_vector &range) + { + PythonConversion::string_converter conv; + bool first = true; + std::stringstream ss; + ss << "["; + for (const auto &item : range.base) { + if (!first) + ss << ", "; + ss << "'" << conv.to_str(range.ctx, item) << "'"; + first = false; + } + ss << "]"; + return ss.str(); + } + + static int len(wrapped_vector &range) { return range.base.capacity(); } + + static py::object getitem(wrapped_vector &range, int i) + { + store_index> idx(i); + if (!range.base.count(idx)) + throw py::none(); + return py::cast(value_conv()(range.ctx, boost::ref(range.base.at(idx)))); + } + + static void wrap(py::module &m, const char *range_name, const char *iter_name) + { + py::class_(m, range_name) + .def("__iter__", iter) + .def("__repr__", repr) + .def("__len__", len) + .def("__getitem__", getitem); + + iterator_wrapper().wrap(m, iter_name); + } + + typedef iterator_wrapper iter_wrap; +}; + +#define WRAP_INDEXSTORE(m, t, conv) \ + indexed_store_wrapper().wrap(m, #t, #t "Iterator") + +/* +Wrapper for a pair, allows accessing either using C++-style members (.first and +.second) or as a Python iterable and indexable object +*/ +template struct pair_wrapper +{ + typedef std::pair T; + + struct pair_iterator_wrapper + { + static py::object next(iter_pair &iter) + { + if (iter.second == 0) { + iter.second++; + return py::cast(iter.first.first); + } else if (iter.second == 1) { + iter.second++; + return py::cast(iter.first.second); + } else { + PyErr_SetString(PyExc_StopIteration, "End of range reached"); + throw py::error_already_set(); + } + } + + static void wrap(py::module &m, const char *python_name) + { + py::class_>(m, python_name).def("__next__", next); + } + }; + + static py::object get(T &x, int i) + { + if ((i >= 2) || (i < 0)) + KeyError(); + return (i == 1) ? py::object(x.second) : py::object(x.first); + } + + static void set(T &x, int i, py::object val) + { + if ((i >= 2) || (i < 0)) + KeyError(); + if (i == 0) + x.first = val.cast(); + if (i == 1) + x.second = val.cast(); + } + + static int len(T &x) { return 2; } + + static iter_pair iter(T &x) { return iter_pair(boost::ref(x), 0); }; + + static void wrap(py::module &m, const char *pair_name, const char *iter_name) + { + pair_iterator_wrapper::wrap(m, iter_name); + py::class_(m, pair_name) + .def("__iter__", iter) + .def("__len__", len) + .def("__getitem__", get) + .def("__setitem__", set, py::keep_alive<1, 2>()) + .def_readwrite("first", &T::first) + .def_readwrite("second", &T::second); + } +}; + +/* +Special case of above for map key/values + */ +template struct map_pair_wrapper +{ + typedef std::pair T; + typedef PythonConversion::ContextualWrapper wrapped_pair; + typedef typename T::second_type V; + + struct pair_iterator_wrapper + { + static py::object next(iter_pair &iter) + { + if (iter.second == 0) { + iter.second++; + return py::cast(PythonConversion::string_converter().to_str( + iter.first.ctx, iter.first.base.first)); + } else if (iter.second == 1) { + iter.second++; + return py::cast(value_conv()(iter.first.ctx, iter.first.base.second)); + } else { + PyErr_SetString(PyExc_StopIteration, "End of range reached"); + throw py::error_already_set(); + } + } + + static void wrap(py::module &m, const char *python_name) + { + py::class_>(m, python_name).def("__next__", next); + } + }; + + static py::object get(wrapped_pair &x, int i) + { + if ((i >= 2) || (i < 0)) + KeyError(); + return (i == 1) ? py::cast(value_conv()(x.ctx, x.base.second)) + : py::cast(PythonConversion::string_converter().to_str(x.ctx, + x.base.first)); + } + + static int len(wrapped_pair &x) { return 2; } + + static iter_pair iter(wrapped_pair &x) + { + return iter_pair(boost::ref(x), 0); + }; + + static std::string first_getter(wrapped_pair &t) + { + return PythonConversion::string_converter().to_str(t.ctx, t.base.first); + } + + static typename value_conv::ret_type second_getter(wrapped_pair &t) { return value_conv()(t.ctx, t.base.second); } + + static void wrap(py::module &m, const char *pair_name, const char *iter_name) + { + pair_iterator_wrapper::wrap(m, iter_name); + py::class_(m, pair_name) + .def("__iter__", iter) + .def("__len__", len) + .def("__getitem__", get) + .def_property_readonly("first", first_getter) + .def_property_readonly("second", second_getter); + } +}; + +/* +Wrapper for a map, either an unordered_map, regular map or dict + */ + +template struct map_wrapper +{ + typedef typename std::remove_cv::type>::type K; + typedef typename T::mapped_type V; + typedef typename value_conv::ret_type wrapped_V; + typedef typename T::value_type KV; + typedef typename PythonConversion::ContextualWrapper wrapped_map; + + static wrapped_V get(wrapped_map &x, std::string const &i) + { + K k = PythonConversion::string_converter().from_str(x.ctx, i); + if (x.base.find(k) != x.base.end()) + return value_conv()(x.ctx, x.base.at(k)); + KeyError(); + + // Should be unreachable, but prevent control may reach end of non-void + throw std::runtime_error("unreachable"); + } + + static void set(wrapped_map &x, std::string const &i, V const &v) + { + x.base[PythonConversion::string_converter().from_str(x.ctx, i)] = v; + } + + static size_t len(wrapped_map &x) { return x.base.size(); } + + static void del(T const &x, std::string const &i) + { + K k = PythonConversion::string_converter().from_str(x.ctx, i); + if (x.base.find(k) != x.base.end()) + x.base.erase(k); + else + KeyError(); + } + + static bool contains(wrapped_map &x, std::string const &i) + { + K k = PythonConversion::string_converter().from_str(x.ctx, i); + return x.base.count(k); + } + + static void wrap(py::module &m, const char *map_name, const char *kv_name, const char *kv_iter_name, + const char *iter_name) + { + map_pair_wrapper::wrap(m, kv_name, kv_iter_name); + typedef range_wrapper> rw; + typename rw::iter_wrap().wrap(m, iter_name); + py::class_(m, map_name) + .def("__iter__", rw::iter) + .def("__len__", len) + .def("__contains__", contains) + .def("__getitem__", get) + .def("__setitem__", set, py::keep_alive<1, 2>()); + } +}; + +/* +Special case of above for map key/values where value is a unique_ptr + */ +template struct map_pair_wrapper_uptr +{ + typedef std::pair T; + typedef PythonConversion::ContextualWrapper wrapped_pair; + typedef typename T::second_type::element_type V; + + struct pair_iterator_wrapper + { + static py::object next(iter_pair &iter) + { + if (iter.second == 0) { + iter.second++; + return py::cast(PythonConversion::string_converter().to_str( + iter.first.ctx, iter.first.base.first)); + } else if (iter.second == 1) { + iter.second++; + return py::cast( + PythonConversion::ContextualWrapper(iter.first.ctx, *iter.first.base.second.get())); + } else { + PyErr_SetString(PyExc_StopIteration, "End of range reached"); + throw py::error_already_set(); + } + } + + static void wrap(py::module &m, const char *python_name) + { + py::class_>(m, python_name).def("__next__", next); + } + }; + + static py::object get(wrapped_pair &x, int i) + { + if ((i >= 2) || (i < 0)) + KeyError(); + return (i == 1) ? py::cast(PythonConversion::ContextualWrapper(x.ctx, *x.base.second.get())) + : py::cast(PythonConversion::string_converter().to_str(x.ctx, + x.base.first)); + } + + static int len(wrapped_pair &x) { return 2; } + + static iter_pair iter(wrapped_pair &x) + { + return iter_pair(boost::ref(x), 0); + }; + + static std::string first_getter(wrapped_pair &t) + { + return PythonConversion::string_converter().to_str(t.ctx, t.base.first); + } + + static PythonConversion::ContextualWrapper second_getter(wrapped_pair &t) + { + return PythonConversion::ContextualWrapper(t.ctx, *t.base.second.get()); + } + + static void wrap(py::module &m, const char *pair_name, const char *iter_name) + { + pair_iterator_wrapper::wrap(m, iter_name); + py::class_(m, pair_name) + .def("__iter__", iter) + .def("__len__", len) + .def("__getitem__", get) + .def_property_readonly("first", first_getter) + .def_property_readonly("second", second_getter); + } +}; + +/* +Wrapper for a map, either an unordered_map, regular map or dict + */ + +template struct map_wrapper_uptr +{ + typedef typename std::remove_cv::type>::type K; + typedef typename T::mapped_type::pointer V; + typedef typename T::mapped_type::element_type &Vr; + typedef typename T::value_type KV; + typedef typename PythonConversion::ContextualWrapper wrapped_map; + + static PythonConversion::ContextualWrapper get(wrapped_map &x, std::string const &i) + { + K k = PythonConversion::string_converter().from_str(x.ctx, i); + if (x.base.find(k) != x.base.end()) + return PythonConversion::ContextualWrapper(x.ctx, *x.base.at(k).get()); + KeyError(); + + // Should be unreachable, but prevent control may reach end of non-void + throw std::runtime_error("unreachable"); + } + + static void set(wrapped_map &x, std::string const &i, V const &v) + { + x.base[PythonConversion::string_converter().from_str(x.ctx, i)] = typename T::mapped_type(v); + } + + static size_t len(wrapped_map &x) { return x.base.size(); } + + static void del(T const &x, std::string const &i) + { + K k = PythonConversion::string_converter().from_str(x.ctx, i); + if (x.base.find(k) != x.base.end()) + x.base.erase(k); + else + KeyError(); + } + + static bool contains(wrapped_map &x, std::string const &i) + { + K k = PythonConversion::string_converter().from_str(x.ctx, i); + return x.base.count(k); + } + + static void wrap(py::module &m, const char *map_name, const char *kv_name, const char *kv_iter_name, + const char *iter_name) + { + map_pair_wrapper_uptr::wrap(m, kv_name, kv_iter_name); + typedef range_wrapper> rw; + typename rw::iter_wrap().wrap(m, iter_name); + py::class_(m, map_name) + .def("__iter__", rw::iter) + .def("__len__", len) + .def("__contains__", contains) + .def("__getitem__", get) + .def("__setitem__", set, py::keep_alive<1, 2>()); + } +}; + +#define WRAP_MAP(m, t, conv, name) \ + map_wrapper().wrap(m, #name, #name "KeyValue", #name "KeyValueIter", #name "Iterator") +#define WRAP_MAP_UPTR(m, t, name) \ + map_wrapper_uptr().wrap(m, #name, #name "KeyValue", #name "KeyValueIter", #name "Iterator") + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/kernel/pywrappers.h b/common/kernel/pywrappers.h new file mode 100644 index 00000000..60ef65be --- /dev/null +++ b/common/kernel/pywrappers.h @@ -0,0 +1,463 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * 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 PYWRAPPERS_H +#define PYWRAPPERS_H + +#include +#include +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace py = pybind11; + +namespace PythonConversion { +template struct ContextualWrapper +{ + Context *ctx; + T base; + + inline ContextualWrapper(Context *c, T x) : ctx(c), base(x){}; + + inline operator T() { return base; }; + typedef T base_type; +}; + +template struct WrapIfNotContext +{ + typedef ContextualWrapper maybe_wrapped_t; +}; + +template <> struct WrapIfNotContext +{ + typedef Context maybe_wrapped_t; +}; + +template inline Context *get_ctx(typename WrapIfNotContext::maybe_wrapped_t &wrp_ctx) +{ + return wrp_ctx.ctx; +} + +template <> inline Context *get_ctx(WrapIfNotContext::maybe_wrapped_t &unwrp_ctx) +{ + return &unwrp_ctx; +} + +template inline T &get_base(typename WrapIfNotContext::maybe_wrapped_t &wrp_ctx) +{ + return wrp_ctx.base; +} + +template <> inline Context &get_base(WrapIfNotContext::maybe_wrapped_t &unwrp_ctx) +{ + return unwrp_ctx; +} + +template ContextualWrapper wrap_ctx(Context *ctx, T x) { return ContextualWrapper(ctx, x); } + +// Dummy class, to be implemented by users +template struct string_converter; + +class bad_wrap +{ +}; + +// Action options +template struct pass_through +{ + inline T operator()(Context *ctx, T x) { return x; } + + using ret_type = T; + using arg_type = T; +}; + +template struct wrap_context +{ + inline ContextualWrapper operator()(Context *ctx, T x) { return ContextualWrapper(ctx, x); } + + using arg_type = T; + using ret_type = ContextualWrapper; +}; + +template struct unwrap_context +{ + inline T operator()(Context *ctx, ContextualWrapper x) { return x.base; } + + using ret_type = T; + using arg_type = ContextualWrapper; +}; + +template struct conv_from_str +{ + inline T operator()(Context *ctx, std::string x) { return string_converter().from_str(ctx, x); } + + using ret_type = T; + using arg_type = std::string; +}; + +template struct conv_to_str +{ + inline std::string operator()(Context *ctx, T x) { return string_converter().to_str(ctx, x); } + + using ret_type = std::string; + using arg_type = T; +}; + +template struct deref_and_wrap +{ + inline ContextualWrapper operator()(Context *ctx, T *x) + { + if (x == nullptr) + throw bad_wrap(); + return ContextualWrapper(ctx, *x); + } + + using arg_type = T *; + using ret_type = ContextualWrapper; +}; + +template struct addr_and_unwrap +{ + inline T *operator()(Context *ctx, ContextualWrapper x) { return &(x.base); } + + using arg_type = ContextualWrapper; + using ret_type = T *; +}; + +// Function wrapper +// Zero parameters, one return +template struct fn_wrapper_0a +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + using conv_result_type = typename rv_conv::ret_type; + + static py::object wrapped_fn(class_type &cls) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + try { + return py::cast(rv_conv()(ctx, (base.*fn)())); + } catch (bad_wrap &) { + return py::none(); + } + } + + template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } +}; + +// One parameter, one return +template struct fn_wrapper_1a +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + using conv_result_type = typename rv_conv::ret_type; + using conv_arg1_type = typename arg1_conv::arg_type; + + static py::object wrapped_fn(class_type &cls, conv_arg1_type arg1) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + try { + return py::cast(rv_conv()(ctx, (base.*fn)(arg1_conv()(ctx, arg1)))); + } catch (bad_wrap &) { + return py::none(); + } + } + + template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } +}; + +// Two parameters, one return +template +struct fn_wrapper_2a +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + using conv_result_type = typename rv_conv::ret_type; + using conv_arg1_type = typename arg1_conv::arg_type; + using conv_arg2_type = typename arg2_conv::arg_type; + + static py::object wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + try { + return py::cast(rv_conv()(ctx, (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2)))); + } catch (bad_wrap &) { + return py::none(); + } + } + + template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } +}; + +// Three parameters, one return +template +struct fn_wrapper_3a +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + using conv_result_type = typename rv_conv::ret_type; + using conv_arg1_type = typename arg1_conv::arg_type; + using conv_arg2_type = typename arg2_conv::arg_type; + using conv_arg3_type = typename arg3_conv::arg_type; + + static py::object wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + try { + return py::cast( + rv_conv()(ctx, (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3)))); + } catch (bad_wrap &) { + return py::none(); + } + } + + template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } +}; + +// Zero parameters void +template struct fn_wrapper_0a_v +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + + static void wrapped_fn(class_type &cls) + { + Class &base = get_base(cls); + return (base.*fn)(); + } + + template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } +}; + +// One parameter, void +template struct fn_wrapper_1a_v +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + using conv_arg1_type = typename arg1_conv::arg_type; + + static void wrapped_fn(class_type &cls, conv_arg1_type arg1) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + (base.*fn)(arg1_conv()(ctx, arg1)); + } + + template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } + + template + static void def_wrap(WrapCls cls_, const char *name, Ta a = py::arg("arg1")) + { + cls_.def(name, wrapped_fn, a); + } +}; + +// Two parameters, no return +template struct fn_wrapper_2a_v +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + using conv_arg1_type = typename arg1_conv::arg_type; + using conv_arg2_type = typename arg2_conv::arg_type; + + static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2)); + } + + template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } + + template static void def_wrap(WrapCls cls_, const char *name, Ta... a) + { + cls_.def(name, wrapped_fn, a...); + } +}; + +// Three parameters, no return +template +struct fn_wrapper_3a_v +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + using conv_arg1_type = typename arg1_conv::arg_type; + using conv_arg2_type = typename arg2_conv::arg_type; + using conv_arg3_type = typename arg3_conv::arg_type; + + static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3)); + } + + template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } + + template static void def_wrap(WrapCls cls_, const char *name, Ta... a) + { + cls_.def(name, wrapped_fn, a...); + } +}; + +// Four parameters, no return +template +struct fn_wrapper_4a_v +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + using conv_arg1_type = typename arg1_conv::arg_type; + using conv_arg2_type = typename arg2_conv::arg_type; + using conv_arg3_type = typename arg3_conv::arg_type; + using conv_arg4_type = typename arg4_conv::arg_type; + + static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3, + conv_arg4_type arg4) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), arg4_conv()(ctx, arg4)); + } + + template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } + + template static void def_wrap(WrapCls cls_, const char *name, Ta... a) + { + cls_.def(name, wrapped_fn, a...); + } +}; + +// Five parameters, no return +template +struct fn_wrapper_5a_v +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + using conv_arg1_type = typename arg1_conv::arg_type; + using conv_arg2_type = typename arg2_conv::arg_type; + using conv_arg3_type = typename arg3_conv::arg_type; + using conv_arg4_type = typename arg4_conv::arg_type; + using conv_arg5_type = typename arg5_conv::arg_type; + + static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3, + conv_arg4_type arg4, conv_arg5_type arg5) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), arg4_conv()(ctx, arg4), + arg5_conv()(ctx, arg5)); + } + + template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } + + template static void def_wrap(WrapCls cls_, const char *name, Ta... a) + { + cls_.def(name, wrapped_fn, a...); + } +}; + +// Six parameters, no return +template +struct fn_wrapper_6a_v +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + using conv_arg1_type = typename arg1_conv::arg_type; + using conv_arg2_type = typename arg2_conv::arg_type; + using conv_arg3_type = typename arg3_conv::arg_type; + using conv_arg4_type = typename arg4_conv::arg_type; + using conv_arg5_type = typename arg5_conv::arg_type; + using conv_arg6_type = typename arg6_conv::arg_type; + + static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3, + conv_arg4_type arg4, conv_arg5_type arg5, conv_arg6_type arg6) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), arg4_conv()(ctx, arg4), + arg5_conv()(ctx, arg5), arg6_conv()(ctx, arg6)); + } + + template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } + + template static void def_wrap(WrapCls cls_, const char *name, Ta... a) + { + cls_.def(name, wrapped_fn, a...); + } +}; + +// Wrapped getter +template struct readonly_wrapper +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + using conv_val_type = typename v_conv::ret_type; + + static py::object wrapped_getter(class_type &cls) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + try { + return py::cast(v_conv()(ctx, (base.*mem))); + } catch (bad_wrap &) { + return py::none(); + } + } + + template static void def_wrap(WrapCls cls_, const char *name) + { + cls_.def_property_readonly(name, wrapped_getter); + } +}; + +// Wrapped getter/setter +template struct readwrite_wrapper +{ + using class_type = typename WrapIfNotContext::maybe_wrapped_t; + using conv_val_type = typename get_conv::ret_type; + + static py::object wrapped_getter(class_type &cls) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + try { + return py::cast(get_conv()(ctx, (base.*mem))); + } catch (bad_wrap &) { + return py::none(); + } + } + + using conv_arg_type = typename set_conv::arg_type; + + static void wrapped_setter(class_type &cls, conv_arg_type val) + { + Context *ctx = get_ctx(cls); + Class &base = get_base(cls); + (base.*mem) = set_conv()(ctx, val); + } + + template static void def_wrap(WrapCls cls_, const char *name) + { + cls_.def_property(name, wrapped_getter, wrapped_setter); + } +}; + +} // namespace PythonConversion + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/kernel/relptr.h b/common/kernel/relptr.h new file mode 100644 index 00000000..f0f45b7d --- /dev/null +++ b/common/kernel/relptr.h @@ -0,0 +1,74 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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 RELPTR_H +#define RELPTR_H + +#include + +#include "nextpnr_assertions.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +template struct RelPtr +{ + int32_t offset; + + const T *get() const { return reinterpret_cast(reinterpret_cast(this) + offset); } + + const T &operator[](std::size_t index) const { return get()[index]; } + + const T &operator*() const { return *(get()); } + + const T *operator->() const { return get(); } + + RelPtr(const RelPtr &) = delete; + RelPtr &operator=(const RelPtr &) = delete; +}; + +NPNR_PACKED_STRUCT(template struct RelSlice { + int32_t offset; + uint32_t length; + + const T *get() const { return reinterpret_cast(reinterpret_cast(this) + offset); } + + const T &operator[](std::size_t index) const + { + NPNR_ASSERT(index < length); + return get()[index]; + } + + const T *begin() const { return get(); } + const T *end() const { return get() + length; } + + size_t size() const { return length; } + ptrdiff_t ssize() const { return length; } + + const T &operator*() const { return *(get()); } + + const T *operator->() const { return get(); } + + RelSlice(const RelSlice &) = delete; + RelSlice &operator=(const RelSlice &) = delete; +}); + +NEXTPNR_NAMESPACE_END + +#endif /* RELPTR_H */ diff --git a/common/kernel/report.cc b/common/kernel/report.cc new file mode 100644 index 00000000..98ff14fb --- /dev/null +++ b/common/kernel/report.cc @@ -0,0 +1,259 @@ +/* + * 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 "json11.hpp" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +using namespace json11; + +namespace { +dict> get_utilization(const Context *ctx) +{ + // Sort by Bel type + dict> result; + for (auto &cell : ctx->cells) { + result[ctx->getBelBucketName(ctx->getBelBucketForCellType(cell.second.get()->type))].first++; + } + for (auto bel : ctx->getBels()) { + if (!ctx->getBelHidden(bel)) { + result[ctx->getBelBucketName(ctx->getBelBucketForBel(bel))].second++; + } + } + return result; +} +} // namespace + +static std::string clock_event_name(const Context *ctx, const ClockEvent &e) +{ + std::string value; + if (e.clock == ctx->id("$async$")) + value = std::string(""); + else + value = (e.edge == FALLING_EDGE ? std::string("negedge ") : std::string("posedge ")) + e.clock.str(ctx); + return value; +}; + +static Json::array report_critical_paths(const Context *ctx) +{ + + auto report_critical_path = [ctx](const CriticalPath &report) { + Json::array pathJson; + + for (const auto &segment : report.segments) { + + const auto &driver = ctx->cells.at(segment.from.first); + const auto &sink = ctx->cells.at(segment.to.first); + + auto fromLoc = ctx->getBelLocation(driver->bel); + auto toLoc = ctx->getBelLocation(sink->bel); + + auto fromJson = Json::object({{"cell", segment.from.first.c_str(ctx)}, + {"port", segment.from.second.c_str(ctx)}, + {"loc", Json::array({fromLoc.x, fromLoc.y})}}); + + auto toJson = Json::object({{"cell", segment.to.first.c_str(ctx)}, + {"port", segment.to.second.c_str(ctx)}, + {"loc", Json::array({toLoc.x, toLoc.y})}}); + + auto segmentJson = Json::object({ + {"delay", ctx->getDelayNS(segment.delay)}, + {"from", fromJson}, + {"to", toJson}, + }); + + if (segment.type == CriticalPath::Segment::Type::CLK_TO_Q) { + segmentJson["type"] = "clk-to-q"; + } else if (segment.type == CriticalPath::Segment::Type::SOURCE) { + segmentJson["type"] = "source"; + } else if (segment.type == CriticalPath::Segment::Type::LOGIC) { + segmentJson["type"] = "logic"; + } else if (segment.type == CriticalPath::Segment::Type::SETUP) { + segmentJson["type"] = "setup"; + } else if (segment.type == CriticalPath::Segment::Type::ROUTING) { + segmentJson["type"] = "routing"; + segmentJson["net"] = segment.net.c_str(ctx); + segmentJson["budget"] = ctx->getDelayNS(segment.budget); + } + + pathJson.push_back(segmentJson); + } + + return pathJson; + }; + + auto critPathsJson = Json::array(); + + // Critical paths + for (auto &report : ctx->timing_result.clock_paths) { + + critPathsJson.push_back(Json::object({{"from", clock_event_name(ctx, report.second.clock_pair.start)}, + {"to", clock_event_name(ctx, report.second.clock_pair.end)}, + {"path", report_critical_path(report.second)}})); + } + + // Cross-domain paths + for (auto &report : ctx->timing_result.xclock_paths) { + critPathsJson.push_back(Json::object({{"from", clock_event_name(ctx, report.clock_pair.start)}, + {"to", clock_event_name(ctx, report.clock_pair.end)}, + {"path", report_critical_path(report)}})); + } + + return critPathsJson; +} + +static Json::array report_detailed_net_timings(const Context *ctx) +{ + auto detailedNetTimingsJson = Json::array(); + + // Detailed per-net timing analysis + for (const auto &it : ctx->timing_result.detailed_net_timings) { + + const NetInfo *net = ctx->nets.at(it.first).get(); + ClockEvent start = it.second[0].clock_pair.start; + + Json::array endpointsJson; + for (const auto &sink_timing : it.second) { + + // FIXME: Is it possible that there are multiple different start + // events for a single net? It has a single driver + NPNR_ASSERT(sink_timing.clock_pair.start == start); + + auto endpointJson = Json::object({{"cell", sink_timing.cell_port.first.c_str(ctx)}, + {"port", sink_timing.cell_port.second.c_str(ctx)}, + {"event", clock_event_name(ctx, sink_timing.clock_pair.end)}, + {"delay", ctx->getDelayNS(sink_timing.delay)}, + {"budget", ctx->getDelayNS(sink_timing.budget)}}); + endpointsJson.push_back(endpointJson); + } + + auto netTimingJson = Json::object({{"net", net->name.c_str(ctx)}, + {"driver", net->driver.cell->name.c_str(ctx)}, + {"port", net->driver.port.c_str(ctx)}, + {"event", clock_event_name(ctx, start)}, + {"endpoints", endpointsJson}}); + + detailedNetTimingsJson.push_back(netTimingJson); + } + + return detailedNetTimingsJson; +} + +/* +Report JSON structure: + +{ + "utilization": { + : { + "available": , + "used": + }, + ... + }, + "fmax" { + : { + "achieved": , + "constraint": + }, + ... + }, + "critical_paths": [ + { + "from": , + "to": , + "path": [ + { + "from": { + "cell": + "port": + "loc": [ + , + + ] + }, + "to": { + "cell": + "port": + "loc": [ + , + + ] + }, + "type": , + "net": , + "delay": , + "budget": , + } + ... + ] + }, + ... + ], + "detailed_net_timings": [ + { + "driver": , + "port": , + "event": , + "net": , + "endpoints": [ + { + "cell": , + "port": , + "event": , + "delay": , + "budget": , + } + ... + ] + } + ... + ] +} +*/ + +void Context::writeReport(std::ostream &out) const +{ + auto util = get_utilization(this); + dict util_json; + for (const auto &kv : util) { + util_json[kv.first.str(this)] = Json::object{ + {"used", kv.second.first}, + {"available", kv.second.second}, + }; + } + dict fmax_json; + for (const auto &kv : timing_result.clock_fmax) { + fmax_json[kv.first.str(this)] = Json::object{ + {"achieved", kv.second.achieved}, + {"constraint", kv.second.constraint}, + }; + } + + Json::object jsonRoot{ + {"utilization", util_json}, {"fmax", fmax_json}, {"critical_paths", report_critical_paths(this)}}; + + if (detailed_timing_report) { + jsonRoot["detailed_net_timings"] = report_detailed_net_timings(this); + } + + out << Json(jsonRoot).dump() << std::endl; +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/scope_lock.h b/common/kernel/scope_lock.h new file mode 100644 index 00000000..2f0f767c --- /dev/null +++ b/common/kernel/scope_lock.h @@ -0,0 +1,67 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 Symbiflow Authors. + * + * 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 SCOPE_LOCK_H +#define SCOPE_LOCK_H + +#include + +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +// Provides a simple RAII locking object. ScopeLock takes a lock when +// constructed, and releases the lock on destruction or if "unlock_early" is +// called. +// +// LockingObject must have a method "void lock(void)" and "void unlock(void)". +template class ScopeLock +{ + public: + ScopeLock(LockingObject *obj) : obj_(obj), locked_(false) + { + obj_->lock(); + locked_ = true; + } + ScopeLock(const ScopeLock &other) = delete; + ScopeLock(const ScopeLock &&other) = delete; + + ~ScopeLock() + { + if (locked_) { + obj_->unlock(); + } + } + void unlock_early() + { + if (!locked_) { + throw std::runtime_error("Lock already released?"); + } + locked_ = false; + obj_->unlock(); + } + + private: + LockingObject *obj_; + bool locked_; +}; + +NEXTPNR_NAMESPACE_END + +#endif /* SCOPE_LOCK_H */ diff --git a/common/kernel/sdf.cc b/common/kernel/sdf.cc new file mode 100644 index 00000000..acff56ed --- /dev/null +++ b/common/kernel/sdf.cc @@ -0,0 +1,334 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 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" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace SDF { + +struct MinMaxTyp +{ + double min, typ, max; +}; + +struct RiseFallDelay +{ + MinMaxTyp rise, fall; +}; + +struct PortAndEdge +{ + std::string port; + ClockEdge edge; +}; + +struct IOPath +{ + std::string from, to; + RiseFallDelay delay; +}; + +struct TimingCheck +{ + enum CheckType + { + SETUPHOLD, + PERIOD, + WIDTH + } type; + PortAndEdge from, to; + RiseFallDelay delay; +}; + +struct Cell +{ + std::string celltype, instance; + std::vector iopaths; + std::vector checks; +}; + +struct CellPort +{ + std::string cell, port; +}; + +struct Interconnect +{ + CellPort from, to; + RiseFallDelay delay; +}; + +struct SDFWriter +{ + bool cvc_mode = false; + std::vector cells; + std::vector conn; + std::string sdfversion, design, vendor, program; + + std::string format_name(const std::string &name) + { + std::string fmt = "\""; + for (char c : name) { + if (c == '\\' || c == '\"') + fmt += "\""; + fmt += c; + } + fmt += "\""; + return fmt; + } + + std::string escape_name(const std::string &name) + { + std::string esc; + for (char c : name) { + if (c == '$' || c == '\\' || c == '[' || c == ']' || c == ':' || (cvc_mode && c == '.')) + esc += '\\'; + esc += c; + } + return esc; + } + + std::string timing_check_name(TimingCheck::CheckType type) + { + switch (type) { + case TimingCheck::SETUPHOLD: + return "SETUPHOLD"; + case TimingCheck::PERIOD: + return "PERIOD"; + case TimingCheck::WIDTH: + return "WIDTH"; + default: + NPNR_ASSERT_FALSE("unknown timing check type"); + } + } + + void write_delay(std::ostream &out, const RiseFallDelay &delay) + { + write_delay(out, delay.rise); + out << " "; + write_delay(out, delay.fall); + } + + void write_delay(std::ostream &out, const MinMaxTyp &delay) + { + if (cvc_mode) + out << "(" << int(delay.min) << ":" << int(delay.typ) << ":" << int(delay.max) << ")"; + else + out << "(" << delay.min << ":" << delay.typ << ":" << delay.max << ")"; + } + + void write_port(std::ostream &out, const CellPort &port) + { + if (cvc_mode) + out << escape_name(port.cell) + "." + escape_name(port.port); + else + out << escape_name(port.cell + "/" + port.port); + } + + void write_portedge(std::ostream &out, const PortAndEdge &pe) + { + out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << escape_name(pe.port) << ")"; + } + + void write(std::ostream &out) + { + out << "(DELAYFILE" << std::endl; + // Headers and metadata + out << " (SDFVERSION " << format_name(sdfversion) << ")" << std::endl; + out << " (DESIGN " << format_name(design) << ")" << std::endl; + out << " (VENDOR " << format_name(vendor) << ")" << std::endl; + out << " (PROGRAM " << format_name(program) << ")" << std::endl; + out << " (DIVIDER " << (cvc_mode ? "." : "/") << ")" << std::endl; + out << " (TIMESCALE 1ps)" << std::endl; + // Write interconnect delays, with the main design begin a "cell" + out << " (CELL" << std::endl; + out << " (CELLTYPE " << format_name(design) << ")" << std::endl; + out << " (INSTANCE )" << std::endl; + out << " (DELAY" << std::endl; + out << " (ABSOLUTE" << std::endl; + for (auto &ic : conn) { + out << " (INTERCONNECT "; + write_port(out, ic.from); + out << " "; + write_port(out, ic.to); + out << " "; + write_delay(out, ic.delay); + out << ")" << std::endl; + } + out << " )" << std::endl; + out << " )" << std::endl; + out << " )" << std::endl; + // Write cells + for (auto &cell : cells) { + out << " (CELL" << std::endl; + out << " (CELLTYPE " << format_name(cell.celltype) << ")" << std::endl; + out << " (INSTANCE " << escape_name(cell.instance) << ")" << std::endl; + // IOPATHs (combinational delay and clock-to-q) + if (!cell.iopaths.empty()) { + out << " (DELAY" << std::endl; + out << " (ABSOLUTE" << std::endl; + for (auto &path : cell.iopaths) { + out << " (IOPATH " << escape_name(path.from) << " " << escape_name(path.to) << " "; + write_delay(out, path.delay); + out << ")" << std::endl; + } + out << " )" << std::endl; + out << " )" << std::endl; + } + // Timing Checks (setup/hold, period, width) + if (!cell.checks.empty()) { + out << " (TIMINGCHECK" << std::endl; + for (auto &check : cell.checks) { + out << " (" << timing_check_name(check.type) << " "; + write_portedge(out, check.from); + out << " "; + if (check.type == TimingCheck::SETUPHOLD) { + write_portedge(out, check.to); + out << " "; + } + if (check.type == TimingCheck::SETUPHOLD) + write_delay(out, check.delay); + else + write_delay(out, check.delay.rise); + out << ")" << std::endl; + } + out << " )" << std::endl; + } + out << " )" << std::endl; + } + out << ")" << std::endl; + } +}; + +} // namespace SDF + +void Context::writeSDF(std::ostream &out, bool cvc_mode) const +{ + using namespace SDF; + SDFWriter wr; + wr.cvc_mode = cvc_mode; + wr.design = str_or_default(attrs, id("module"), "top"); + wr.sdfversion = "3.0"; + wr.vendor = "nextpnr"; + wr.program = "nextpnr"; + + const double delay_scale = 1000; + // Convert from DelayQuad to SDF-friendly RiseFallDelay + auto convert_delay = [&](const DelayQuad &dly) { + RiseFallDelay rf; + rf.rise.min = getDelayNS(dly.minRiseDelay()) * delay_scale; + rf.rise.typ = getDelayNS((dly.minRiseDelay() + dly.maxRiseDelay()) / 2) * delay_scale; // fixme: typ delays? + rf.rise.max = getDelayNS(dly.maxRiseDelay()) * delay_scale; + rf.fall.min = getDelayNS(dly.minFallDelay()) * delay_scale; + rf.fall.typ = getDelayNS((dly.minFallDelay() + dly.maxFallDelay()) / 2) * delay_scale; // fixme: typ delays? + rf.fall.max = getDelayNS(dly.maxFallDelay()) * delay_scale; + return rf; + }; + + auto convert_setuphold = [&](const DelayPair &setup, const DelayPair &hold) { + RiseFallDelay rf; + rf.rise.min = getDelayNS(setup.minDelay()) * delay_scale; + rf.rise.typ = getDelayNS((setup.minDelay() + setup.maxDelay()) / 2) * delay_scale; // fixme: typ delays? + rf.rise.max = getDelayNS(setup.maxDelay()) * delay_scale; + rf.fall.min = getDelayNS(hold.minDelay()) * delay_scale; + rf.fall.typ = getDelayNS((hold.minDelay() + hold.maxDelay()) / 2) * delay_scale; // fixme: typ delays? + rf.fall.max = getDelayNS(hold.maxDelay()) * delay_scale; + return rf; + }; + + for (const auto &cell : cells) { + Cell sc; + const CellInfo *ci = cell.second.get(); + sc.instance = ci->name.str(this); + sc.celltype = ci->type.str(this); + for (auto port : ci->ports) { + int clockCount = 0; + TimingPortClass cls = getPortTimingClass(ci, port.first, clockCount); + if (cls == TMG_IGNORE) + continue; + if (port.second.net == nullptr) + continue; // Ignore disconnected ports + if (port.second.type != PORT_IN) { + // Add combinational paths to this output (or inout) + for (auto other : ci->ports) { + if (other.second.net == nullptr) + continue; + if (other.second.type == PORT_OUT) + continue; + DelayQuad dly; + if (!getCellDelay(ci, other.first, port.first, dly)) + continue; + IOPath iop; + iop.from = other.first.str(this); + iop.to = port.first.str(this); + iop.delay = convert_delay(dly); + sc.iopaths.push_back(iop); + } + // Add clock-to-output delays, also as IOPaths + if (cls == TMG_REGISTER_OUTPUT) + for (int i = 0; i < clockCount; i++) { + auto clkInfo = getPortClockingInfo(ci, port.first, i); + IOPath cqp; + cqp.from = clkInfo.clock_port.str(this); + cqp.to = port.first.str(this); + cqp.delay = convert_delay(clkInfo.clockToQ); + sc.iopaths.push_back(cqp); + } + } + if (port.second.type != PORT_OUT && cls == TMG_REGISTER_INPUT) { + // Add setup/hold checks + for (int i = 0; i < clockCount; i++) { + auto clkInfo = getPortClockingInfo(ci, port.first, i); + TimingCheck chk; + chk.from.edge = RISING_EDGE; // Add setup/hold checks equally for rising and falling edges + chk.from.port = port.first.str(this); + chk.to.edge = clkInfo.edge; + chk.to.port = clkInfo.clock_port.str(this); + chk.type = TimingCheck::SETUPHOLD; + chk.delay = convert_setuphold(clkInfo.setup, clkInfo.hold); + sc.checks.push_back(chk); + chk.from.edge = FALLING_EDGE; + sc.checks.push_back(chk); + } + } + } + wr.cells.push_back(sc); + } + + for (auto &net : nets) { + NetInfo *ni = net.second.get(); + if (ni->driver.cell == nullptr) + continue; + for (auto &usr : ni->users) { + Interconnect ic; + ic.from.cell = ni->driver.cell->name.str(this); + ic.from.port = ni->driver.port.str(this); + ic.to.cell = usr.cell->name.str(this); + ic.to.port = usr.port.str(this); + // FIXME: min/max routing delay + ic.delay = convert_delay(DelayQuad(getNetinfoRouteDelay(ni, usr))); + wr.conn.push_back(ic); + } + } + wr.write(out); +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/sso_array.h b/common/kernel/sso_array.h new file mode 100644 index 00000000..80e7d1c1 --- /dev/null +++ b/common/kernel/sso_array.h @@ -0,0 +1,132 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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 SSO_ARRAY_H +#define SSO_ARRAY_H + +#include + +#include "nextpnr_assertions.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +// An small size optimised array that is statically allocated when the size is N or less; heap allocated otherwise +template class SSOArray +{ + private: + union + { + T data_static[N]; + T *data_heap; + }; + std::size_t m_size; + inline bool is_heap() const { return (m_size > N); } + void alloc() + { + if (is_heap()) { + data_heap = new T[m_size]; + } + } + + public: + T *data() { return is_heap() ? data_heap : data_static; } + const T *data() const { return is_heap() ? data_heap : data_static; } + std::size_t size() const { return m_size; } + + T *begin() { return data(); } + T *end() { return data() + m_size; } + const T *begin() const { return data(); } + const T *end() const { return data() + m_size; } + + SSOArray() : m_size(0){}; + + SSOArray(std::size_t size, const T &init = T()) : m_size(size) + { + alloc(); + std::fill(begin(), end(), init); + } + + SSOArray(const SSOArray &other) : m_size(other.size()) + { + alloc(); + std::copy(other.begin(), other.end(), begin()); + } + + SSOArray(SSOArray &&other) : m_size(other.size()) + { + if (is_heap()) + data_heap = other.data_heap; + else + std::copy(other.begin(), other.end(), begin()); + other.m_size = 0; + } + SSOArray &operator=(const SSOArray &other) + { + if (&other == this) + return *this; + if (is_heap()) + delete[] data_heap; + m_size = other.m_size; + alloc(); + std::copy(other.begin(), other.end(), begin()); + return *this; + } + + template SSOArray(const Tother &other) : m_size(other.size()) + { + alloc(); + std::copy(other.begin(), other.end(), begin()); + } + + ~SSOArray() + { + if (is_heap()) { + delete[] data_heap; + } + } + + bool operator==(const SSOArray &other) const + { + if (size() != other.size()) + return false; + return std::equal(begin(), end(), other.begin()); + } + bool operator!=(const SSOArray &other) const + { + if (size() != other.size()) + return true; + return !std::equal(begin(), end(), other.begin()); + } + T &operator[](std::size_t idx) + { + NPNR_ASSERT(idx < m_size); + return data()[idx]; + } + const T &operator[](std::size_t idx) const + { + NPNR_ASSERT(idx < m_size); + return data()[idx]; + } +}; + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/kernel/str_ring_buffer.cc b/common/kernel/str_ring_buffer.cc new file mode 100644 index 00000000..443d8612 --- /dev/null +++ b/common/kernel/str_ring_buffer.cc @@ -0,0 +1,34 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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 "str_ring_buffer.h" + +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +std::string &StrRingBuffer::next() +{ + std::string &s = buffer.at(index++); + if (index >= N) + index = 0; + return s; +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/str_ring_buffer.h b/common/kernel/str_ring_buffer.h new file mode 100644 index 00000000..42583beb --- /dev/null +++ b/common/kernel/str_ring_buffer.h @@ -0,0 +1,45 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 Serge Bazanski + * + * 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 STR_RING_BUFFER_H +#define STR_RING_BUFFER_H + +#include +#include + +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +// A ring buffer of strings, so we can return a simple const char * pointer for %s formatting - inspired by how logging +// in Yosys works Let's just hope noone tries to log more than 100 things in one call.... +class StrRingBuffer +{ + private: + static const size_t N = 100; + std::array buffer; + size_t index = 0; + + public: + std::string &next(); +}; + +NEXTPNR_NAMESPACE_END + +#endif /* STR_RING_BUFFER_H */ diff --git a/common/kernel/svg.cc b/common/kernel/svg.cc new file mode 100644 index 00000000..c5e2ea36 --- /dev/null +++ b/common/kernel/svg.cc @@ -0,0 +1,152 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 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 +#include +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN +namespace { +struct SVGWriter +{ + const Context *ctx; + std::ostream &out; + float scale = 500.0; + bool hide_inactive = false; + SVGWriter(const Context *ctx, std::ostream &out) : ctx(ctx), out(out){}; + const char *get_stroke_colour(GraphicElement::style_t style) + { + switch (style) { + case GraphicElement::STYLE_GRID: + return "#CCC"; + case GraphicElement::STYLE_FRAME: + return "#808080"; + case GraphicElement::STYLE_INACTIVE: + return "#C0C0C0"; + case GraphicElement::STYLE_ACTIVE: + return "#FF3030"; + default: + return "#000"; + } + } + + void write_decal(const DecalXY &dxy) + { + for (const auto &el : ctx->getDecalGraphics(dxy.decal)) { + if (el.style == GraphicElement::STYLE_HIDDEN || + (hide_inactive && el.style == GraphicElement::STYLE_INACTIVE)) + continue; + switch (el.type) { + case GraphicElement::TYPE_LINE: + case GraphicElement::TYPE_ARROW: + case GraphicElement::TYPE_LOCAL_LINE: + case GraphicElement::TYPE_LOCAL_ARROW: + out << stringf("", (el.x1 + dxy.x) * scale, + (el.y1 + dxy.y) * scale, (el.x2 + dxy.x) * scale, (el.y2 + dxy.y) * scale, + get_stroke_colour(el.style)) + << std::endl; + break; + case GraphicElement::TYPE_BOX: + out << stringf("", + (el.x1 + dxy.x) * scale, (el.y1 + dxy.y) * scale, (el.x2 - el.x1) * scale, + (el.y2 - el.y1) * scale, get_stroke_colour(el.style), + el.style == GraphicElement::STYLE_ACTIVE ? "#FF8080" : "none") + << std::endl; + break; + default: + break; + } + } + } + + void operator()(const std::string &flags) + { + std::vector options; + boost::algorithm::split(options, flags, boost::algorithm::is_space()); + bool noroute = false; + for (const auto &opt : options) { + if (boost::algorithm::starts_with(opt, "scale=")) { + scale = float(std::stod(opt.substr(6))); + continue; + } else if (opt == "hide_routing") { + noroute = true; + } else if (opt == "hide_inactive") { + hide_inactive = true; + } else { + log_error("Unknown SVG option '%s'\n", opt.c_str()); + } + } + float max_x = 0, max_y = 0; + for (auto group : ctx->getGroups()) { + auto decal = ctx->getGroupDecal(group); + for (auto el : ctx->getDecalGraphics(decal.decal)) { + max_x = std::max(max_x, decal.x + el.x1 + 1); + max_y = std::max(max_y, decal.y + el.y1 + 1); + } + } + for (auto bel : ctx->getBels()) { + auto decal = ctx->getBelDecal(bel); + for (auto el : ctx->getDecalGraphics(decal.decal)) { + max_x = std::max(max_x, decal.x + el.x1 + 1); + max_y = std::max(max_y, decal.y + el.y1 + 1); + } + } + for (auto wire : ctx->getWires()) { + auto decal = ctx->getWireDecal(wire); + for (auto el : ctx->getDecalGraphics(decal.decal)) { + max_x = std::max(max_x, decal.x + el.x1 + 1); + max_y = std::max(max_y, decal.y + el.y1 + 1); + } + } + for (auto pip : ctx->getPips()) { + auto decal = ctx->getPipDecal(pip); + for (auto el : ctx->getDecalGraphics(decal.decal)) { + max_x = std::max(max_x, decal.x + el.x1 + 1); + max_y = std::max(max_y, decal.y + el.y1 + 1); + } + } + out << "" << std::endl; + out << stringf("", + max_x * scale, max_y * scale, max_x * scale, max_y * scale) + << std::endl; + out << "" << std::endl; + for (auto group : ctx->getGroups()) + write_decal(ctx->getGroupDecal(group)); + for (auto bel : ctx->getBels()) + write_decal(ctx->getBelDecal(bel)); + if (!noroute) { + for (auto wire : ctx->getWires()) + write_decal(ctx->getWireDecal(wire)); + for (auto pip : ctx->getPips()) + write_decal(ctx->getPipDecal(pip)); + } + out << "" << std::endl; + } +}; +} // namespace + +void Context::writeSVG(const std::string &filename, const std::string &flags) const +{ + std::ofstream out(filename); + SVGWriter(this, out)(flags); +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/timing.cc b/common/kernel/timing.cc new file mode 100644 index 00000000..834785fb --- /dev/null +++ b/common/kernel/timing.cc @@ -0,0 +1,1515 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 gatecat + * Copyright (C) 2018 Eddie Hung + * + * 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 "timing.h" +#include +#include +#include +#include +#include +#include "log.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +void TimingAnalyser::setup() +{ + init_ports(); + get_cell_delays(); + topo_sort(); + setup_port_domains(); + run(); +} + +void TimingAnalyser::run(bool update_route_delays) +{ + reset_times(); + if (update_route_delays) + get_route_delays(); + walk_forward(); + walk_backward(); + compute_slack(); + compute_criticality(); +} + +void TimingAnalyser::init_ports() +{ + // Per cell port structures + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + for (auto &port : ci->ports) { + auto &data = ports[CellPortKey(ci->name, port.first)]; + data.type = port.second.type; + data.cell_port = CellPortKey(ci->name, port.first); + } + } +} + +void TimingAnalyser::get_cell_delays() +{ + for (auto &port : ports) { + CellInfo *ci = cell_info(port.first); + auto &pi = port_info(port.first); + auto &pd = port.second; + + IdString name = port.first.port; + // Ignore dangling ports altogether for timing purposes + if (!pi.net) + continue; + pd.cell_arcs.clear(); + int clkInfoCount = 0; + TimingPortClass cls = ctx->getPortTimingClass(ci, name, clkInfoCount); + if (cls == TMG_STARTPOINT || cls == TMG_ENDPOINT || cls == TMG_CLOCK_INPUT || cls == TMG_GEN_CLOCK || + cls == TMG_IGNORE) + continue; + if (pi.type == PORT_IN) { + // Input ports might have setup/hold relationships + if (cls == TMG_REGISTER_INPUT) { + for (int i = 0; i < clkInfoCount; i++) { + auto info = ctx->getPortClockingInfo(ci, name, i); + if (!ci->ports.count(info.clock_port) || ci->ports.at(info.clock_port).net == nullptr) + continue; + pd.cell_arcs.emplace_back(CellArc::SETUP, info.clock_port, DelayQuad(info.setup, info.setup), + info.edge); + pd.cell_arcs.emplace_back(CellArc::HOLD, info.clock_port, DelayQuad(info.hold, info.hold), + info.edge); + } + } + // Combinational delays through cell + for (auto &other_port : ci->ports) { + auto &op = other_port.second; + // ignore dangling ports and non-outputs + if (op.net == nullptr || op.type != PORT_OUT) + continue; + DelayQuad delay; + bool is_path = ctx->getCellDelay(ci, name, other_port.first, delay); + if (is_path) + pd.cell_arcs.emplace_back(CellArc::COMBINATIONAL, other_port.first, delay); + } + } else if (pi.type == PORT_OUT) { + // Output ports might have clk-to-q relationships + if (cls == TMG_REGISTER_OUTPUT) { + for (int i = 0; i < clkInfoCount; i++) { + auto info = ctx->getPortClockingInfo(ci, name, i); + if (!ci->ports.count(info.clock_port) || ci->ports.at(info.clock_port).net == nullptr) + continue; + pd.cell_arcs.emplace_back(CellArc::CLK_TO_Q, info.clock_port, info.clockToQ, info.edge); + } + } + // Combinational delays through cell + for (auto &other_port : ci->ports) { + auto &op = other_port.second; + // ignore dangling ports and non-inputs + if (op.net == nullptr || op.type != PORT_IN) + continue; + DelayQuad delay; + bool is_path = ctx->getCellDelay(ci, other_port.first, name, delay); + if (is_path) + pd.cell_arcs.emplace_back(CellArc::COMBINATIONAL, other_port.first, delay); + } + } + } +} + +void TimingAnalyser::get_route_delays() +{ + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (ni->driver.cell == nullptr || ni->driver.cell->bel == BelId()) + continue; + for (auto &usr : ni->users) { + if (usr.cell->bel == BelId()) + continue; + ports.at(CellPortKey(usr)).route_delay = DelayPair(ctx->getNetinfoRouteDelay(ni, usr)); + } + } +} + +void TimingAnalyser::set_route_delay(CellPortKey port, DelayPair value) { ports.at(port).route_delay = value; } + +void TimingAnalyser::topo_sort() +{ + TopoSort topo; + for (auto &port : ports) { + auto &pd = port.second; + // All ports are nodes + topo.node(port.first); + if (pd.type == PORT_IN) { + // inputs: combinational arcs through the cell are edges + for (auto &arc : pd.cell_arcs) { + if (arc.type != CellArc::COMBINATIONAL) + continue; + topo.edge(port.first, CellPortKey(port.first.cell, arc.other_port)); + } + } else if (pd.type == PORT_OUT) { + // output: routing arcs are edges + const NetInfo *pn = port_info(port.first).net; + if (pn != nullptr) { + for (auto &usr : pn->users) + topo.edge(port.first, CellPortKey(usr)); + } + } + } + bool no_loops = topo.sort(); + if (!no_loops && verbose_mode) { + log_info("Found %d combinational loops:\n", int(topo.loops.size())); + int i = 0; + for (auto &loop : topo.loops) { + log_info(" loop %d:\n", ++i); + for (auto &port : loop) { + log_info(" %s.%s (%s)\n", ctx->nameOf(port.cell), ctx->nameOf(port.port), + ctx->nameOf(port_info(port).net)); + } + } + } + have_loops = !no_loops; + std::swap(topological_order, topo.sorted); +} + +void TimingAnalyser::setup_port_domains() +{ + for (auto &d : domains) { + d.startpoints.clear(); + d.endpoints.clear(); + } + // Go forward through the topological order (domains from the PoV of arrival time) + bool first_iter = true; + do { + updated_domains = false; + for (auto port : topological_order) { + auto &pd = ports.at(port); + auto &pi = port_info(port); + if (pi.type == PORT_OUT) { + if (first_iter) { + for (auto &fanin : pd.cell_arcs) { + if (fanin.type != CellArc::CLK_TO_Q) + continue; + // registered outputs are startpoints + auto dom = domain_id(port.cell, fanin.other_port, fanin.edge); + // create per-domain data + pd.arrival[dom]; + domains.at(dom).startpoints.emplace_back(port, fanin.other_port); + } + } + // copy domains across routing + if (pi.net != nullptr) + for (auto &usr : pi.net->users) + copy_domains(port, CellPortKey(usr), false); + } else { + // copy domains from input to output + for (auto &fanout : pd.cell_arcs) { + if (fanout.type != CellArc::COMBINATIONAL) + continue; + copy_domains(port, CellPortKey(port.cell, fanout.other_port), false); + } + } + } + // Go backward through the topological order (domains from the PoV of required time) + for (auto port : reversed_range(topological_order)) { + auto &pd = ports.at(port); + auto &pi = port_info(port); + if (pi.type == PORT_OUT) { + // copy domains from output to input + for (auto &fanin : pd.cell_arcs) { + if (fanin.type != CellArc::COMBINATIONAL) + continue; + copy_domains(port, CellPortKey(port.cell, fanin.other_port), true); + } + } else { + if (first_iter) { + for (auto &fanout : pd.cell_arcs) { + if (fanout.type != CellArc::SETUP) + continue; + // registered inputs are endpoints + auto dom = domain_id(port.cell, fanout.other_port, fanout.edge); + // create per-domain data + pd.required[dom]; + domains.at(dom).endpoints.emplace_back(port, fanout.other_port); + } + } + // copy port to driver + if (pi.net != nullptr && pi.net->driver.cell != nullptr) + copy_domains(port, CellPortKey(pi.net->driver), true); + } + } + // Iterate over ports and find domain paris + for (auto port : topological_order) { + auto &pd = ports.at(port); + for (auto &arr : pd.arrival) + for (auto &req : pd.required) { + pd.domain_pairs[domain_pair_id(arr.first, req.first)]; + } + } + first_iter = false; + // If there are loops, repeat the process until a fixed point is reached, as there might be unusual ways to + // visit points, which would result in a missing domain key and therefore crash later on + } while (have_loops && updated_domains); + for (auto &dp : domain_pairs) { + auto &launch_data = domains.at(dp.key.launch); + auto &capture_data = domains.at(dp.key.capture); + if (launch_data.key.clock != capture_data.key.clock) + continue; + IdString clk = launch_data.key.clock; + delay_t period = ctx->getDelayFromNS(1.0e9 / ctx->setting("target_freq")); + if (ctx->nets.count(clk)) { + NetInfo *clk_net = ctx->nets.at(clk).get(); + if (clk_net->clkconstr) { + period = clk_net->clkconstr->period.minDelay(); + } + } + if (launch_data.key.edge != capture_data.key.edge) + period /= 2; + dp.period = DelayPair(period); + } +} + +void TimingAnalyser::reset_times() +{ + for (auto &port : ports) { + auto do_reset = [&](dict ×) { + for (auto &t : times) { + t.second.value = init_delay; + t.second.path_length = 0; + t.second.bwd_min = CellPortKey(); + t.second.bwd_max = CellPortKey(); + } + }; + do_reset(port.second.arrival); + do_reset(port.second.required); + for (auto &dp : port.second.domain_pairs) { + dp.second.setup_slack = std::numeric_limits::max(); + dp.second.hold_slack = std::numeric_limits::max(); + dp.second.max_path_length = 0; + dp.second.criticality = 0; + dp.second.budget = 0; + } + port.second.worst_crit = 0; + port.second.worst_setup_slack = std::numeric_limits::max(); + port.second.worst_hold_slack = std::numeric_limits::max(); + } +} + +void TimingAnalyser::set_arrival_time(CellPortKey target, domain_id_t domain, DelayPair arrival, int path_length, + CellPortKey prev) +{ + auto &arr = ports.at(target).arrival.at(domain); + if (arrival.max_delay > arr.value.max_delay) { + arr.value.max_delay = arrival.max_delay; + arr.bwd_max = prev; + } + if (!setup_only && (arrival.min_delay < arr.value.min_delay)) { + arr.value.min_delay = arrival.min_delay; + arr.bwd_min = prev; + } + arr.path_length = std::max(arr.path_length, path_length); +} + +void TimingAnalyser::set_required_time(CellPortKey target, domain_id_t domain, DelayPair required, int path_length, + CellPortKey prev) +{ + auto &req = ports.at(target).required.at(domain); + if (required.min_delay < req.value.min_delay) { + req.value.min_delay = required.min_delay; + req.bwd_min = prev; + } + if (!setup_only && (required.max_delay > req.value.max_delay)) { + req.value.max_delay = required.max_delay; + req.bwd_max = prev; + } + req.path_length = std::max(req.path_length, path_length); +} + +void TimingAnalyser::walk_forward() +{ + // Assign initial arrival time to domain startpoints + for (domain_id_t dom_id = 0; dom_id < domain_id_t(domains.size()); ++dom_id) { + auto &dom = domains.at(dom_id); + for (auto &sp : dom.startpoints) { + auto &pd = ports.at(sp.first); + DelayPair init_arrival(0); + CellPortKey clock_key; + // TODO: clock routing delay, if analysis of that is enabled + if (sp.second != IdString()) { + // clocked startpoints have a clock-to-out time + for (auto &fanin : pd.cell_arcs) { + if (fanin.type == CellArc::CLK_TO_Q && fanin.other_port == sp.second) { + init_arrival = init_arrival + fanin.value.delayPair(); + break; + } + } + clock_key = CellPortKey(sp.first.cell, sp.second); + } + set_arrival_time(sp.first, dom_id, init_arrival, 1, clock_key); + } + } + // Walk forward in topological order + for (auto p : topological_order) { + auto &pd = ports.at(p); + for (auto &arr : pd.arrival) { + if (pd.type == PORT_OUT) { + // Output port: propagate delay through net, adding route delay + NetInfo *net = port_info(p).net; + if (net != nullptr) + for (auto &usr : net->users) { + CellPortKey usr_key(usr); + auto &usr_pd = ports.at(usr_key); + set_arrival_time(usr_key, arr.first, arr.second.value + usr_pd.route_delay, + arr.second.path_length, p); + } + } else if (pd.type == PORT_IN) { + // Input port; propagate delay through cell, adding combinational delay + for (auto &fanout : pd.cell_arcs) { + if (fanout.type != CellArc::COMBINATIONAL) + continue; + set_arrival_time(CellPortKey(p.cell, fanout.other_port), arr.first, + arr.second.value + fanout.value.delayPair(), arr.second.path_length + 1, p); + } + } + } + } +} + +void TimingAnalyser::walk_backward() +{ + // Assign initial required time to domain endpoints + // Note that clock frequency will be considered later in the analysis for, for now all required times are normalised + // to 0ns + for (domain_id_t dom_id = 0; dom_id < domain_id_t(domains.size()); ++dom_id) { + auto &dom = domains.at(dom_id); + for (auto &ep : dom.endpoints) { + auto &pd = ports.at(ep.first); + DelayPair init_setuphold(0); + CellPortKey clock_key; + // TODO: clock routing delay, if analysis of that is enabled + if (ep.second != IdString()) { + // Add setup/hold time, if this endpoint is clocked + for (auto &fanin : pd.cell_arcs) { + if (fanin.type == CellArc::SETUP && fanin.other_port == ep.second) + init_setuphold.min_delay -= fanin.value.maxDelay(); + if (fanin.type == CellArc::HOLD && fanin.other_port == ep.second) + init_setuphold.max_delay -= fanin.value.maxDelay(); + } + clock_key = CellPortKey(ep.first.cell, ep.second); + } + set_required_time(ep.first, dom_id, init_setuphold, 1, clock_key); + } + } + // Walk backwards in topological order + for (auto p : reversed_range(topological_order)) { + auto &pd = ports.at(p); + for (auto &req : pd.required) { + if (pd.type == PORT_IN) { + // Input port: propagate delay back through net, subtracting route delay + NetInfo *net = port_info(p).net; + if (net != nullptr && net->driver.cell != nullptr) + set_required_time(CellPortKey(net->driver), req.first, + req.second.value - DelayPair(pd.route_delay.maxDelay()), req.second.path_length, + p); + } else if (pd.type == PORT_OUT) { + // Output port : propagate delay back through cell, subtracting combinational delay + for (auto &fanin : pd.cell_arcs) { + if (fanin.type != CellArc::COMBINATIONAL) + continue; + set_required_time(CellPortKey(p.cell, fanin.other_port), req.first, + req.second.value - DelayPair(fanin.value.maxDelay()), req.second.path_length + 1, + p); + } + } + } + } +} + +void TimingAnalyser::print_fmax() +{ + // Temporary testing code for comparison only + dict domain_fmax; + for (auto p : topological_order) { + auto &pd = ports.at(p); + for (auto &req : pd.required) { + if (pd.arrival.count(req.first)) { + auto &arr = pd.arrival.at(req.first); + double fmax = 1000.0 / ctx->getDelayNS(arr.value.maxDelay() - req.second.value.minDelay()); + if (!domain_fmax.count(req.first) || domain_fmax.at(req.first) > fmax) + domain_fmax[req.first] = fmax; + } + } + } + for (auto &fm : domain_fmax) { + log_info("Domain %s Worst Fmax %.02f\n", ctx->nameOf(domains.at(fm.first).key.clock), fm.second); + } +} + +void TimingAnalyser::compute_slack() +{ + for (auto &dp : domain_pairs) { + dp.worst_setup_slack = std::numeric_limits::max(); + dp.worst_hold_slack = std::numeric_limits::max(); + } + for (auto p : topological_order) { + auto &pd = ports.at(p); + for (auto &pdp : pd.domain_pairs) { + auto &dp = domain_pairs.at(pdp.first); + auto &arr = pd.arrival.at(dp.key.launch); + auto &req = pd.required.at(dp.key.capture); + pdp.second.setup_slack = 0 - (arr.value.maxDelay() - req.value.minDelay()); + if (!setup_only) + pdp.second.hold_slack = arr.value.minDelay() - req.value.maxDelay(); + pdp.second.max_path_length = arr.path_length + req.path_length; + if (dp.key.launch == dp.key.capture) + pd.worst_setup_slack = std::min(pd.worst_setup_slack, dp.period.minDelay() + pdp.second.setup_slack); + dp.worst_setup_slack = std::min(dp.worst_setup_slack, pdp.second.setup_slack); + if (!setup_only) { + pd.worst_hold_slack = std::min(pd.worst_hold_slack, pdp.second.hold_slack); + dp.worst_hold_slack = std::min(dp.worst_hold_slack, pdp.second.hold_slack); + } + } + } +} + +void TimingAnalyser::compute_criticality() +{ + for (auto p : topological_order) { + auto &pd = ports.at(p); + for (auto &pdp : pd.domain_pairs) { + auto &dp = domain_pairs.at(pdp.first); + float crit = + 1.0f - (float(pdp.second.setup_slack) - float(dp.worst_setup_slack)) / float(-dp.worst_setup_slack); + crit = std::min(crit, 1.0f); + crit = std::max(crit, 0.0f); + pdp.second.criticality = crit; + pd.worst_crit = std::max(pd.worst_crit, crit); + } + } +} + +std::vector TimingAnalyser::get_failing_eps(domain_id_t domain_pair, int count) +{ + std::vector failing_eps; + delay_t last_slack = std::numeric_limits::min(); + auto &dp = domain_pairs.at(domain_pair); + auto &cap_d = domains.at(dp.key.capture); + while (int(failing_eps.size()) < count) { + CellPortKey next; + delay_t next_slack = std::numeric_limits::max(); + for (auto ep : cap_d.endpoints) { + auto &pd = ports.at(ep.first); + if (!pd.domain_pairs.count(domain_pair)) + continue; + delay_t ep_slack = pd.domain_pairs.at(domain_pair).setup_slack; + if (ep_slack < next_slack && ep_slack > last_slack) { + next = ep.first; + next_slack = ep_slack; + } + } + if (next == CellPortKey()) + break; + failing_eps.push_back(next); + last_slack = next_slack; + } + return failing_eps; +} + +void TimingAnalyser::print_critical_path(CellPortKey endpoint, domain_id_t domain_pair) +{ + CellPortKey cursor = endpoint; + auto &dp = domain_pairs.at(domain_pair); + log(" endpoint %s.%s (slack %.02fns):\n", ctx->nameOf(cursor.cell), ctx->nameOf(cursor.port), + ctx->getDelayNS(ports.at(cursor).domain_pairs.at(domain_pair).setup_slack)); + while (cursor != CellPortKey()) { + log(" %s.%s (net %s)\n", ctx->nameOf(cursor.cell), ctx->nameOf(cursor.port), + ctx->nameOf(get_net_or_empty(ctx->cells.at(cursor.cell).get(), cursor.port))); + if (!ports.at(cursor).arrival.count(dp.key.launch)) + break; + cursor = ports.at(cursor).arrival.at(dp.key.launch).bwd_max; + } +} + +namespace { +const char *edge_name(ClockEdge edge) { return (edge == FALLING_EDGE) ? "negedge" : "posedge"; } +} // namespace + +void TimingAnalyser::print_report() +{ + for (int i = 0; i < int(domain_pairs.size()); i++) { + auto &dp = domain_pairs.at(i); + auto &launch = domains.at(dp.key.launch); + auto &capture = domains.at(dp.key.capture); + log("Worst endpoints for %s %s -> %s %s\n", edge_name(launch.key.edge), ctx->nameOf(launch.key.clock), + edge_name(capture.key.edge), ctx->nameOf(capture.key.clock)); + auto failing_eps = get_failing_eps(i, 5); + for (auto &ep : failing_eps) + print_critical_path(ep, i); + log_break(); + } +} + +domain_id_t TimingAnalyser::domain_id(IdString cell, IdString clock_port, ClockEdge edge) +{ + return domain_id(ctx->cells.at(cell)->ports.at(clock_port).net, edge); +} +domain_id_t TimingAnalyser::domain_id(const NetInfo *net, ClockEdge edge) +{ + NPNR_ASSERT(net != nullptr); + ClockDomainKey key{net->name, edge}; + auto inserted = domain_to_id.emplace(key, domains.size()); + if (inserted.second) { + domains.emplace_back(key); + } + return inserted.first->second; +} +domain_id_t TimingAnalyser::domain_pair_id(domain_id_t launch, domain_id_t capture) +{ + ClockDomainPairKey key{launch, capture}; + auto inserted = pair_to_id.emplace(key, domain_pairs.size()); + if (inserted.second) { + domain_pairs.emplace_back(key); + } + return inserted.first->second; +} + +void TimingAnalyser::copy_domains(const CellPortKey &from, const CellPortKey &to, bool backward) +{ + auto &f = ports.at(from), &t = ports.at(to); + for (auto &dom : (backward ? f.required : f.arrival)) { + updated_domains |= (backward ? t.required : t.arrival).emplace(dom.first, ArrivReqTime{}).second; + } +} + +CellInfo *TimingAnalyser::cell_info(const CellPortKey &key) { return ctx->cells.at(key.cell).get(); } + +PortInfo &TimingAnalyser::port_info(const CellPortKey &key) { return ctx->cells.at(key.cell)->ports.at(key.port); } + +/** LEGACY CODE BEGIN **/ + +typedef std::vector PortRefVector; +typedef std::map DelayFrequency; + +struct CriticalPathData +{ + PortRefVector ports; + delay_t path_delay; + delay_t path_period; +}; + +typedef dict CriticalPathDataMap; + +typedef dict> DetailedNetTimings; + +struct Timing +{ + Context *ctx; + bool net_delays; + bool update; + delay_t min_slack; + CriticalPathDataMap *crit_path; + DelayFrequency *slack_histogram; + DetailedNetTimings *detailed_net_timings; + IdString async_clock; + + struct TimingData + { + TimingData() : max_arrival(), max_path_length(), min_remaining_budget() {} + TimingData(delay_t max_arrival) : max_arrival(max_arrival), max_path_length(), min_remaining_budget() {} + delay_t max_arrival; + unsigned max_path_length = 0; + delay_t min_remaining_budget; + bool false_startpoint = false; + std::vector min_required; + dict arrival_time; + }; + + Timing(Context *ctx, bool net_delays, bool update, CriticalPathDataMap *crit_path = nullptr, + DelayFrequency *slack_histogram = nullptr, DetailedNetTimings *detailed_net_timings = nullptr) + : ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->setting("target_freq")), + crit_path(crit_path), slack_histogram(slack_histogram), detailed_net_timings(detailed_net_timings), + async_clock(ctx->id("$async$")) + { + } + + delay_t walk_paths() + { + const auto clk_period = ctx->getDelayFromNS(1.0e9 / ctx->setting("target_freq")); + + // First, compute the topological order of nets to walk through the circuit, assuming it is a _acyclic_ graph + // TODO(eddieh): Handle the case where it is cyclic, e.g. combinatorial loops + std::vector topological_order; + dict, hash_ptr_ops> net_data; + // In lieu of deleting edges from the graph, simply count the number of fanins to each output port + dict port_fanin; + + std::vector input_ports; + std::vector output_ports; + + pool ooc_port_nets; + + // In out-of-context mode, top-level inputs look floating but aren't + if (bool_or_default(ctx->settings, ctx->id("arch.ooc"))) { + for (auto &p : ctx->ports) { + if (p.second.type != PORT_IN || p.second.net == nullptr) + continue; + ooc_port_nets.insert(p.second.net->name); + } + } + + for (auto &cell : ctx->cells) { + input_ports.clear(); + output_ports.clear(); + for (auto &port : cell.second->ports) { + if (!port.second.net) + continue; + if (port.second.type == PORT_OUT) + output_ports.push_back(&port.second); + else + input_ports.push_back(port.first); + } + + for (auto o : output_ports) { + int clocks = 0; + TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clocks); + // If output port is influenced by a clock (e.g. FF output) then add it to the ordering as a timing + // start-point + if (portClass == TMG_REGISTER_OUTPUT) { + topological_order.emplace_back(o->net); + for (int i = 0; i < clocks; i++) { + TimingClockingInfo clkInfo = ctx->getPortClockingInfo(cell.second.get(), o->name, i); + const NetInfo *clknet = get_net_or_empty(cell.second.get(), clkInfo.clock_port); + IdString clksig = clknet ? clknet->name : async_clock; + net_data[o->net][ClockEvent{clksig, clknet ? clkInfo.edge : RISING_EDGE}] = + TimingData{clkInfo.clockToQ.maxDelay()}; + } + + } else { + if (portClass == TMG_STARTPOINT || portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE) { + topological_order.emplace_back(o->net); + TimingData td; + td.false_startpoint = (portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE); + td.max_arrival = 0; + net_data[o->net][ClockEvent{async_clock, RISING_EDGE}] = td; + } + + // Don't analyse paths from a clock input to other pins - they will be considered by the + // special-case handling register input/output class ports + if (portClass == TMG_CLOCK_INPUT) + continue; + + // Otherwise, for all driven input ports on this cell, if a timing arc exists between the input and + // the current output port, increment fanin counter + for (auto i : input_ports) { + DelayQuad comb_delay; + NetInfo *i_net = cell.second->ports[i].net; + if (i_net->driver.cell == nullptr && !ooc_port_nets.count(i_net->name)) + continue; + bool is_path = ctx->getCellDelay(cell.second.get(), i, o->name, comb_delay); + if (is_path) + port_fanin[o]++; + } + // If there is no fanin, add the port as a false startpoint + if (!port_fanin.count(o) && !net_data.count(o->net)) { + topological_order.emplace_back(o->net); + TimingData td; + td.false_startpoint = true; + td.max_arrival = 0; + net_data[o->net][ClockEvent{async_clock, RISING_EDGE}] = td; + } + } + } + } + + // In out-of-context mode, handle top-level ports correctly + if (bool_or_default(ctx->settings, ctx->id("arch.ooc"))) { + for (auto &p : ctx->ports) { + if (p.second.type != PORT_IN || p.second.net == nullptr) + continue; + topological_order.emplace_back(p.second.net); + } + } + + std::deque queue(topological_order.begin(), topological_order.end()); + // Now walk the design, from the start points identified previously, building up a topological order + while (!queue.empty()) { + const auto net = queue.front(); + queue.pop_front(); + + for (auto &usr : net->users) { + int user_clocks; + TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, user_clocks); + if (usrClass == TMG_IGNORE || usrClass == TMG_CLOCK_INPUT) + continue; + for (auto &port : usr.cell->ports) { + if (port.second.type != PORT_OUT || !port.second.net) + continue; + int port_clocks; + TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, port_clocks); + + // Skip if this is a clocked output (but allow non-clocked ones) + if (portClass == TMG_REGISTER_OUTPUT || portClass == TMG_STARTPOINT || portClass == TMG_IGNORE || + portClass == TMG_GEN_CLOCK) + continue; + DelayQuad comb_delay; + bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); + if (!is_path) + continue; + // Decrement the fanin count, and only add to topological order if all its fanins have already + // been visited + auto it = port_fanin.find(&port.second); + if (it == port_fanin.end()) + log_error("Timing counted negative fanin count for port %s.%s (net %s), please report this " + "error.\n", + ctx->nameOf(usr.cell), ctx->nameOf(port.first), ctx->nameOf(port.second.net)); + if (--it->second == 0) { + topological_order.emplace_back(port.second.net); + queue.emplace_back(port.second.net); + port_fanin.erase(it); + } + } + } + } + + // Sanity check to ensure that all ports where fanins were recorded were indeed visited + if (!port_fanin.empty() && !bool_or_default(ctx->settings, ctx->id("timing/ignoreLoops"), false)) { + for (auto fanin : port_fanin) { + NetInfo *net = fanin.first->net; + if (net != nullptr) { + log_info(" remaining fanin includes %s (net %s)\n", fanin.first->name.c_str(ctx), + net->name.c_str(ctx)); + if (net->driver.cell != nullptr) + log_info(" driver = %s.%s\n", net->driver.cell->name.c_str(ctx), + net->driver.port.c_str(ctx)); + for (auto net_user : net->users) + log_info(" user: %s.%s\n", net_user.cell->name.c_str(ctx), net_user.port.c_str(ctx)); + } else { + log_info(" remaining fanin includes %s (no net)\n", fanin.first->name.c_str(ctx)); + } + } + if (ctx->force) + log_warning("timing analysis failed due to presence of combinatorial loops, incomplete specification " + "of timing ports, etc.\n"); + else + log_error("timing analysis failed due to presence of combinatorial loops, incomplete specification of " + "timing ports, etc.\n"); + } + + // Go forwards topologically to find the maximum arrival time and max path length for each net + std::vector startdomains; + for (auto net : topological_order) { + if (!net_data.count(net)) + continue; + // Updates later on might invalidate a reference taken here to net_data, so iterate over a list of domains + // instead + startdomains.clear(); + { + auto &nd_map = net_data.at(net); + for (auto &startdomain : nd_map) + startdomains.push_back(startdomain.first); + } + for (auto &start_clk : startdomains) { + auto &nd = net_data.at(net).at(start_clk); + if (nd.false_startpoint) + continue; + const auto net_arrival = nd.max_arrival; + const auto net_length_plus_one = nd.max_path_length + 1; + nd.min_remaining_budget = clk_period; + for (auto &usr : net->users) { + int port_clocks; + TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks); + auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); + auto usr_arrival = net_arrival + net_delay; + + if (portClass == TMG_ENDPOINT || portClass == TMG_IGNORE || portClass == TMG_CLOCK_INPUT) { + // Skip + } else { + auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); + // Iterate over all output ports on the same cell as the sink + for (auto port : usr.cell->ports) { + if (port.second.type != PORT_OUT || !port.second.net) + continue; + DelayQuad comb_delay; + // Look up delay through this path + bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); + if (!is_path) + continue; + auto &data = net_data[port.second.net][start_clk]; + auto &arrival = data.max_arrival; + arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay()); + if (!budget_override) { // Do not increment path length if budget overridden since it + // doesn't + // require a share of the slack + auto &path_length = data.max_path_length; + path_length = std::max(path_length, net_length_plus_one); + } + } + } + } + } + } + + dict> crit_nets; + + // Now go backwards topologically to determine the minimum path slack, and to distribute all path slack evenly + // between all nets on the path + for (auto net : boost::adaptors::reverse(topological_order)) { + if (!net_data.count(net)) + continue; + auto &nd_map = net_data.at(net); + for (auto &startdomain : nd_map) { + auto &nd = startdomain.second; + // Ignore false startpoints + if (nd.false_startpoint) + continue; + const delay_t net_length_plus_one = nd.max_path_length + 1; + auto &net_min_remaining_budget = nd.min_remaining_budget; + for (auto &usr : net->users) { + auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); + auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); + int port_clocks; + TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks); + if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) { + auto process_endpoint = [&](IdString clksig, ClockEdge edge, delay_t setup) { + const auto net_arrival = nd.max_arrival; + const auto endpoint_arrival = net_arrival + net_delay + setup; + delay_t period; + // Set default period + if (edge == startdomain.first.edge) { + period = clk_period; + } else { + period = clk_period / 2; + } + if (clksig != async_clock) { + if (ctx->nets.at(clksig)->clkconstr) { + if (edge == startdomain.first.edge) { + // same edge + period = ctx->nets.at(clksig)->clkconstr->period.minDelay(); + } else if (edge == RISING_EDGE) { + // falling -> rising + period = ctx->nets.at(clksig)->clkconstr->low.minDelay(); + } else if (edge == FALLING_EDGE) { + // rising -> falling + period = ctx->nets.at(clksig)->clkconstr->high.minDelay(); + } + } + } + auto path_budget = period - endpoint_arrival; + + if (update) { + auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; + usr.budget = std::min(usr.budget, net_delay + budget_share); + net_min_remaining_budget = + std::min(net_min_remaining_budget, path_budget - budget_share); + } + + if (path_budget < min_slack) + min_slack = path_budget; + + if (slack_histogram) { + int slack_ps = ctx->getDelayNS(path_budget) * 1000; + (*slack_histogram)[slack_ps]++; + } + ClockEvent dest_ev{clksig, edge}; + ClockPair clockPair{startdomain.first, dest_ev}; + nd.arrival_time[dest_ev] = std::max(nd.arrival_time[dest_ev], endpoint_arrival); + + // Store the detailed timing for each net and user (a.k.a. sink) + if (detailed_net_timings) { + NetSinkTiming sink_timing; + sink_timing.clock_pair = clockPair; + sink_timing.cell_port = std::make_pair(usr.cell->name, usr.port); + sink_timing.delay = endpoint_arrival; + sink_timing.budget = period; + + (*detailed_net_timings)[net->name].push_back(sink_timing); + } + + if (crit_path) { + if (!crit_nets.count(clockPair) || crit_nets.at(clockPair).first < endpoint_arrival) { + crit_nets[clockPair] = std::make_pair(endpoint_arrival, net); + (*crit_path)[clockPair].path_delay = endpoint_arrival; + (*crit_path)[clockPair].path_period = period; + (*crit_path)[clockPair].ports.clear(); + (*crit_path)[clockPair].ports.push_back(&usr); + } + } + }; + if (portClass == TMG_REGISTER_INPUT) { + for (int i = 0; i < port_clocks; i++) { + TimingClockingInfo clkInfo = ctx->getPortClockingInfo(usr.cell, usr.port, i); + const NetInfo *clknet = get_net_or_empty(usr.cell, clkInfo.clock_port); + IdString clksig = clknet ? clknet->name : async_clock; + process_endpoint(clksig, clknet ? clkInfo.edge : RISING_EDGE, clkInfo.setup.maxDelay()); + } + } else { + process_endpoint(async_clock, RISING_EDGE, 0); + } + + } else if (update) { + + // Iterate over all output ports on the same cell as the sink + for (const auto &port : usr.cell->ports) { + if (port.second.type != PORT_OUT || !port.second.net) + continue; + DelayQuad comb_delay; + bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); + if (!is_path) + continue; + if (net_data.count(port.second.net) && + net_data.at(port.second.net).count(startdomain.first)) { + auto path_budget = + net_data.at(port.second.net).at(startdomain.first).min_remaining_budget; + auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; + usr.budget = std::min(usr.budget, net_delay + budget_share); + net_min_remaining_budget = + std::min(net_min_remaining_budget, path_budget - budget_share); + } + } + } + } + } + } + + if (crit_path) { + // Walk backwards from the most critical net + for (auto crit_pair : crit_nets) { + NetInfo *crit_net = crit_pair.second.second; + auto &cp_ports = (*crit_path)[crit_pair.first].ports; + while (crit_net) { + const PortInfo *crit_ipin = nullptr; + delay_t max_arrival = std::numeric_limits::min(); + // Look at all input ports on its driving cell + for (const auto &port : crit_net->driver.cell->ports) { + if (port.second.type != PORT_IN || !port.second.net) + continue; + DelayQuad comb_delay; + bool is_path = + ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay); + if (!is_path) + continue; + // If input port is influenced by a clock, skip + int port_clocks; + TimingPortClass portClass = + ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks); + if (portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) + continue; + // And find the fanin net with the latest arrival time + if (net_data.count(port.second.net) && + net_data.at(port.second.net).count(crit_pair.first.start)) { + auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival; + if (net_delays) { + for (auto &user : port.second.net->users) + if (user.port == port.first && user.cell == crit_net->driver.cell) { + net_arrival += ctx->getNetinfoRouteDelay(port.second.net, user); + break; + } + } + net_arrival += comb_delay.maxDelay(); + if (net_arrival > max_arrival) { + max_arrival = net_arrival; + crit_ipin = &port.second; + } + } + } + + if (!crit_ipin) + break; + // Now convert PortInfo* into a PortRef* + for (auto &usr : crit_ipin->net->users) { + if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) { + cp_ports.push_back(&usr); + break; + } + } + crit_net = crit_ipin->net; + } + std::reverse(cp_ports.begin(), cp_ports.end()); + } + } + return min_slack; + } + + void assign_budget() + { + // Clear delays to a very high value first + for (auto &net : ctx->nets) { + for (auto &usr : net.second->users) { + usr.budget = std::numeric_limits::max(); + } + } + + walk_paths(); + } +}; + +void assign_budget(Context *ctx, bool quiet) +{ + if (!quiet) { + log_break(); + log_info("Annotating ports with timing budgets for target frequency %.2f MHz\n", + ctx->setting("target_freq") / 1e6); + } + + Timing timing(ctx, ctx->setting("slack_redist_iter") > 0 /* net_delays */, true /* update */); + timing.assign_budget(); + + if (!quiet || ctx->verbose) { + for (auto &net : ctx->nets) { + for (auto &user : net.second->users) { + // Post-update check + if (!ctx->setting("auto_freq") && user.budget < 0) + log_info("port %s.%s, connected to net '%s', has negative " + "timing budget of %fns\n", + user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx), + ctx->getDelayNS(user.budget)); + else if (ctx->debug) + log_info("port %s.%s, connected to net '%s', has " + "timing budget of %fns\n", + user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx), + ctx->getDelayNS(user.budget)); + } + } + } + + // For slack redistribution, if user has not specified a frequency dynamically adjust the target frequency to be the + // currently achieved maximum + if (ctx->setting("auto_freq") && ctx->setting("slack_redist_iter") > 0) { + delay_t default_slack = delay_t((1.0e9 / ctx->getDelayNS(1)) / ctx->setting("target_freq")); + ctx->settings[ctx->id("target_freq")] = + std::to_string(1.0e9 / ctx->getDelayNS(default_slack - timing.min_slack)); + if (ctx->verbose) + log_info("minimum slack for this assign = %.2f ns, target Fmax for next " + "update = %.2f MHz\n", + ctx->getDelayNS(timing.min_slack), ctx->setting("target_freq") / 1e6); + } + + if (!quiet) + log_info("Checksum: 0x%08x\n", ctx->checksum()); +} + +CriticalPath build_critical_path_report(Context *ctx, ClockPair &clocks, const PortRefVector &crit_path) +{ + + CriticalPath report; + report.clock_pair = clocks; + + auto &front = crit_path.front(); + auto &front_port = front->cell->ports.at(front->port); + auto &front_driver = front_port.net->driver; + + int port_clocks; + auto portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks); + + const CellInfo *last_cell = front->cell; + IdString last_port = front_driver.port; + + int clock_start = -1; + if (portClass == TMG_REGISTER_OUTPUT) { + for (int i = 0; i < port_clocks; i++) { + TimingClockingInfo clockInfo = ctx->getPortClockingInfo(front_driver.cell, front_driver.port, i); + const NetInfo *clknet = get_net_or_empty(front_driver.cell, clockInfo.clock_port); + if (clknet != nullptr && clknet->name == clocks.start.clock && clockInfo.edge == clocks.start.edge) { + last_port = clockInfo.clock_port; + clock_start = i; + break; + } + } + } + + for (auto sink : crit_path) { + auto sink_cell = sink->cell; + auto &port = sink_cell->ports.at(sink->port); + auto net = port.net; + auto &driver = net->driver; + auto driver_cell = driver.cell; + + CriticalPath::Segment seg_logic; + + DelayQuad comb_delay; + if (clock_start != -1) { + auto clockInfo = ctx->getPortClockingInfo(driver_cell, driver.port, clock_start); + comb_delay = clockInfo.clockToQ; + clock_start = -1; + seg_logic.type = CriticalPath::Segment::Type::CLK_TO_Q; + } else if (last_port == driver.port) { + // Case where we start with a STARTPOINT etc + comb_delay = DelayQuad(0); + seg_logic.type = CriticalPath::Segment::Type::SOURCE; + } else { + ctx->getCellDelay(driver_cell, last_port, driver.port, comb_delay); + seg_logic.type = CriticalPath::Segment::Type::LOGIC; + } + + seg_logic.delay = comb_delay.maxDelay(); + seg_logic.budget = 0; + seg_logic.from = std::make_pair(last_cell->name, last_port); + seg_logic.to = std::make_pair(driver_cell->name, driver.port); + seg_logic.net = IdString(); + report.segments.push_back(seg_logic); + + auto net_delay = ctx->getNetinfoRouteDelay(net, *sink); + + CriticalPath::Segment seg_route; + seg_route.type = CriticalPath::Segment::Type::ROUTING; + seg_route.delay = net_delay; + seg_route.budget = sink->budget; + seg_route.from = std::make_pair(driver_cell->name, driver.port); + seg_route.to = std::make_pair(sink_cell->name, sink->port); + seg_route.net = net->name; + report.segments.push_back(seg_route); + + last_cell = sink_cell; + last_port = sink->port; + } + + int clockCount = 0; + auto sinkClass = ctx->getPortTimingClass(crit_path.back()->cell, crit_path.back()->port, clockCount); + if (sinkClass == TMG_REGISTER_INPUT && clockCount > 0) { + auto sinkClockInfo = ctx->getPortClockingInfo(crit_path.back()->cell, crit_path.back()->port, 0); + delay_t setup = sinkClockInfo.setup.maxDelay(); + + CriticalPath::Segment seg_logic; + seg_logic.type = CriticalPath::Segment::Type::SETUP; + seg_logic.delay = setup; + seg_logic.budget = 0; + seg_logic.from = std::make_pair(last_cell->name, last_port); + seg_logic.to = seg_logic.from; + seg_logic.net = IdString(); + report.segments.push_back(seg_logic); + } + + return report; +} + +void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path, bool warn_on_failure, + bool update_results) +{ + auto format_event = [ctx](const ClockEvent &e, int field_width = 0) { + std::string value; + if (e.clock == ctx->id("$async$")) + value = std::string(""); + else + value = (e.edge == FALLING_EDGE ? std::string("negedge ") : std::string("posedge ")) + e.clock.str(ctx); + if (int(value.length()) < field_width) + value.insert(value.length(), field_width - int(value.length()), ' '); + return value; + }; + + CriticalPathDataMap crit_paths; + DelayFrequency slack_histogram; + DetailedNetTimings detailed_net_timings; + + Timing timing(ctx, true /* net_delays */, false /* update */, (print_path || print_fmax) ? &crit_paths : nullptr, + print_histogram ? &slack_histogram : nullptr, + (update_results && ctx->detailed_timing_report) ? &detailed_net_timings : nullptr); + timing.walk_paths(); + + bool report_critical_paths = print_path || print_fmax || update_results; + + dict clock_reports; + std::vector xclock_reports; + dict clock_fmax; + std::set empty_clocks; // set of clocks with no interior paths + + if (report_critical_paths) { + + for (auto path : crit_paths) { + const ClockEvent &a = path.first.start; + const ClockEvent &b = path.first.end; + empty_clocks.insert(a.clock); + empty_clocks.insert(b.clock); + } + for (auto path : crit_paths) { + const ClockEvent &a = path.first.start; + const ClockEvent &b = path.first.end; + if (a.clock != b.clock || a.clock == ctx->id("$async$")) + continue; + double Fmax; + empty_clocks.erase(a.clock); + if (a.edge == b.edge) + Fmax = 1000 / ctx->getDelayNS(path.second.path_delay); + else + Fmax = 500 / ctx->getDelayNS(path.second.path_delay); + if (!clock_fmax.count(a.clock) || Fmax < clock_fmax.at(a.clock).achieved) { + clock_fmax[a.clock].achieved = Fmax; + clock_fmax[a.clock].constraint = 0.0f; // Will be filled later + clock_reports[a.clock] = build_critical_path_report(ctx, path.first, path.second.ports); + clock_reports[a.clock].period = path.second.path_period; + } + } + + for (auto &path : crit_paths) { + const ClockEvent &a = path.first.start; + const ClockEvent &b = path.first.end; + if (a.clock == b.clock && a.clock != ctx->id("$async$")) + continue; + + auto &crit_path = crit_paths.at(path.first).ports; + xclock_reports.push_back(build_critical_path_report(ctx, path.first, crit_path)); + xclock_reports.back().period = path.second.path_period; + } + + if (clock_reports.empty()) { + log_info("No Fmax available; no interior timing paths found in design.\n"); + } + + std::sort(xclock_reports.begin(), xclock_reports.end(), [ctx](const CriticalPath &ra, const CriticalPath &rb) { + const auto &a = ra.clock_pair; + const auto &b = rb.clock_pair; + + if (a.start.clock.str(ctx) < b.start.clock.str(ctx)) + return true; + if (a.start.clock.str(ctx) > b.start.clock.str(ctx)) + return false; + if (a.start.edge < b.start.edge) + return true; + if (a.start.edge > b.start.edge) + return false; + if (a.end.clock.str(ctx) < b.end.clock.str(ctx)) + return true; + if (a.end.clock.str(ctx) > b.end.clock.str(ctx)) + return false; + if (a.end.edge < b.end.edge) + return true; + return false; + }); + + for (auto &clock : clock_reports) { + float target = ctx->setting("target_freq") / 1e6; + if (ctx->nets.at(clock.first)->clkconstr) + target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay()); + clock_fmax[clock.first].constraint = target; + } + } + + // Print critical paths + if (print_path) { + + static auto print_net_source = [ctx](const NetInfo *net) { + // Check if this net is annotated with a source list + auto sources = net->attrs.find(ctx->id("src")); + if (sources == net->attrs.end()) { + // No sources for this net, can't print anything + return; + } + + // Sources are separated by pipe characters. + // There is no guaranteed ordering on sources, so we just print all + auto sourcelist = sources->second.as_string(); + std::vector source_entries; + size_t current = 0, prev = 0; + while ((current = sourcelist.find("|", prev)) != std::string::npos) { + source_entries.emplace_back(sourcelist.substr(prev, current - prev)); + prev = current + 1; + } + // Ensure we emplace the final entry + source_entries.emplace_back(sourcelist.substr(prev, current - prev)); + + // Iterate and print our source list at the correct indentation level + log_info(" Defined in:\n"); + for (auto entry : source_entries) { + log_info(" %s\n", entry.c_str()); + } + }; + + // A helper function for reporting one critical path + auto print_path_report = [ctx](const CriticalPath &path) { + delay_t total = 0, logic_total = 0, route_total = 0; + + log_info("curr total\n"); + for (const auto &segment : path.segments) { + + total += segment.delay; + + if (segment.type == CriticalPath::Segment::Type::CLK_TO_Q || + segment.type == CriticalPath::Segment::Type::SOURCE || + segment.type == CriticalPath::Segment::Type::LOGIC || + segment.type == CriticalPath::Segment::Type::SETUP) { + logic_total += segment.delay; + + const std::string type_name = + (segment.type == CriticalPath::Segment::Type::SETUP) ? "Setup" : "Source"; + + log_info("%4.1f %4.1f %s %s.%s\n", ctx->getDelayNS(segment.delay), ctx->getDelayNS(total), + type_name.c_str(), segment.to.first.c_str(ctx), segment.to.second.c_str(ctx)); + } else if (segment.type == CriticalPath::Segment::Type::ROUTING) { + route_total += segment.delay; + + const auto &driver = ctx->cells.at(segment.from.first); + const auto &sink = ctx->cells.at(segment.to.first); + + auto driver_loc = ctx->getBelLocation(driver->bel); + auto sink_loc = ctx->getBelLocation(sink->bel); + + log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n", ctx->getDelayNS(segment.delay), + ctx->getDelayNS(total), segment.net.c_str(ctx), ctx->getDelayNS(segment.budget), + driver_loc.x, driver_loc.y, sink_loc.x, sink_loc.y); + log_info(" Sink %s.%s\n", segment.to.first.c_str(ctx), segment.to.second.c_str(ctx)); + + const NetInfo *net = ctx->nets.at(segment.net).get(); + + if (ctx->verbose) { + + PortRef sink_ref; + sink_ref.cell = sink.get(); + sink_ref.port = segment.to.second; + sink_ref.budget = segment.budget; + + auto driver_wire = ctx->getNetinfoSourceWire(net); + auto sink_wire = ctx->getNetinfoSinkWire(net, sink_ref, 0); + log_info(" prediction: %f ns estimate: %f ns\n", + ctx->getDelayNS(ctx->predictArcDelay(net, sink_ref)), + ctx->getDelayNS(ctx->estimateDelay(driver_wire, sink_wire))); + auto cursor = sink_wire; + delay_t delay; + while (driver_wire != cursor) { +#ifdef ARCH_ECP5 + if (net->is_global) + break; +#endif + auto it = net->wires.find(cursor); + assert(it != net->wires.end()); + auto pip = it->second.pip; + NPNR_ASSERT(pip != PipId()); + delay = ctx->getPipDelay(pip).maxDelay(); + log_info(" %1.3f %s\n", ctx->getDelayNS(delay), ctx->nameOfPip(pip)); + cursor = ctx->getPipSrcWire(pip); + } + } + + if (!ctx->disable_critical_path_source_print) { + print_net_source(net); + } + } + } + log_info("%.1f ns logic, %.1f ns routing\n", ctx->getDelayNS(logic_total), ctx->getDelayNS(route_total)); + }; + + // Single domain paths + for (auto &clock : clock_reports) { + log_break(); + std::string start = clock.second.clock_pair.start.edge == FALLING_EDGE ? std::string("negedge") + : std::string("posedge"); + std::string end = + clock.second.clock_pair.end.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge"); + log_info("Critical path report for clock '%s' (%s -> %s):\n", clock.first.c_str(ctx), start.c_str(), + end.c_str()); + auto &report = clock.second; + print_path_report(report); + } + + // Cross-domain paths + for (auto &report : xclock_reports) { + log_break(); + std::string start = format_event(report.clock_pair.start); + std::string end = format_event(report.clock_pair.end); + log_info("Critical path report for cross-domain path '%s' -> '%s':\n", start.c_str(), end.c_str()); + print_path_report(report); + } + } + + if (print_fmax) { + log_break(); + + unsigned max_width = 0; + for (auto &clock : clock_reports) + max_width = std::max(max_width, clock.first.str(ctx).size()); + + for (auto &clock : clock_reports) { + const auto &clock_name = clock.first.str(ctx); + const int width = max_width - clock_name.size(); + + float fmax = clock_fmax[clock.first].achieved; + float target = clock_fmax[clock.first].constraint; + bool passed = target < fmax; + + if (!warn_on_failure || passed) + log_info("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "", + clock_name.c_str(), fmax, passed ? "PASS" : "FAIL", target); + else if (bool_or_default(ctx->settings, ctx->id("timing/allowFail"), false)) + log_warning("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "", + clock_name.c_str(), fmax, passed ? "PASS" : "FAIL", target); + else + log_nonfatal_error("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "", + clock_name.c_str(), fmax, passed ? "PASS" : "FAIL", target); + } + for (auto &eclock : empty_clocks) { + if (eclock != ctx->id("$async$")) + log_info("Clock '%s' has no interior paths\n", eclock.c_str(ctx)); + } + log_break(); + + int start_field_width = 0, end_field_width = 0; + for (auto &report : xclock_reports) { + start_field_width = std::max((int)format_event(report.clock_pair.start).length(), start_field_width); + end_field_width = std::max((int)format_event(report.clock_pair.end).length(), end_field_width); + } + + for (auto &report : xclock_reports) { + const ClockEvent &a = report.clock_pair.start; + const ClockEvent &b = report.clock_pair.end; + delay_t path_delay = 0; + for (const auto &segment : report.segments) { + path_delay += segment.delay; + } + auto ev_a = format_event(a, start_field_width), ev_b = format_event(b, end_field_width); + log_info("Max delay %s -> %s: %0.02f ns\n", ev_a.c_str(), ev_b.c_str(), ctx->getDelayNS(path_delay)); + } + log_break(); + } + + if (print_histogram && slack_histogram.size() > 0) { + unsigned num_bins = 20; + unsigned bar_width = 60; + auto min_slack = slack_histogram.begin()->first; + auto max_slack = slack_histogram.rbegin()->first; + auto bin_size = std::max(1, ceil((max_slack - min_slack + 1) / float(num_bins))); + std::vector bins(num_bins); + unsigned max_freq = 0; + for (const auto &i : slack_histogram) { + int bin_idx = int((i.first - min_slack) / bin_size); + if (bin_idx < 0) + bin_idx = 0; + else if (bin_idx >= int(num_bins)) + bin_idx = num_bins - 1; + auto &bin = bins.at(bin_idx); + bin += i.second; + max_freq = std::max(max_freq, bin); + } + bar_width = std::min(bar_width, max_freq); + + log_break(); + log_info("Slack histogram:\n"); + log_info(" legend: * represents %d endpoint(s)\n", max_freq / bar_width); + log_info(" + represents [1,%d) endpoint(s)\n", max_freq / bar_width); + for (unsigned i = 0; i < num_bins; ++i) + log_info("[%6d, %6d) |%s%c\n", min_slack + bin_size * i, min_slack + bin_size * (i + 1), + std::string(bins[i] * bar_width / max_freq, '*').c_str(), + (bins[i] * bar_width) % max_freq > 0 ? '+' : ' '); + } + + // Update timing results in the context + if (update_results) { + auto &results = ctx->timing_result; + + results.clock_fmax = std::move(clock_fmax); + results.clock_paths = std::move(clock_reports); + results.xclock_paths = std::move(xclock_reports); + + results.detailed_net_timings = std::move(detailed_net_timings); + } +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/timing.h b/common/kernel/timing.h new file mode 100644 index 00000000..fe1bcaa8 --- /dev/null +++ b/common/kernel/timing.h @@ -0,0 +1,236 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * 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 TIMING_H +#define TIMING_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct CellPortKey +{ + CellPortKey(){}; + CellPortKey(IdString cell, IdString port) : cell(cell), port(port){}; + explicit CellPortKey(const PortRef &pr) + { + NPNR_ASSERT(pr.cell != nullptr); + cell = pr.cell->name; + port = pr.port; + } + IdString cell, port; + unsigned int hash() const { return mkhash(cell.hash(), port.hash()); } + inline bool operator==(const CellPortKey &other) const { return (cell == other.cell) && (port == other.port); } + inline bool operator!=(const CellPortKey &other) const { return (cell != other.cell) || (port != other.port); } + inline bool operator<(const CellPortKey &other) const + { + return cell == other.cell ? port < other.port : cell < other.cell; + } +}; + +struct ClockDomainKey +{ + IdString clock; + ClockEdge edge; + ClockDomainKey(IdString clock_net, ClockEdge edge) : clock(clock_net), edge(edge){}; + // probably also need something here to deal with constraints + inline bool is_async() const { return clock == IdString(); } + + unsigned int hash() const { return mkhash(clock.hash(), int(edge)); } + + inline bool operator==(const ClockDomainKey &other) const { return (clock == other.clock) && (edge == other.edge); } +}; + +typedef int domain_id_t; + +struct ClockDomainPairKey +{ + domain_id_t launch, capture; + ClockDomainPairKey(domain_id_t launch, domain_id_t capture) : launch(launch), capture(capture){}; + inline bool operator==(const ClockDomainPairKey &other) const + { + return (launch == other.launch) && (capture == other.capture); + } + unsigned int hash() const { return mkhash(launch, capture); } +}; + +struct TimingAnalyser +{ + public: + TimingAnalyser(Context *ctx) : ctx(ctx){}; + void setup(); + void run(bool update_route_delays = true); + void print_report(); + + // This is used when routers etc are not actually binding detailed routing (due to congestion or an abstracted + // model), but want to re-run STA with their own calculated delays + void set_route_delay(CellPortKey port, DelayPair value); + + float get_criticality(CellPortKey port) const { return ports.at(port).worst_crit; } + float get_setup_slack(CellPortKey port) const { return ports.at(port).worst_setup_slack; } + float get_domain_setup_slack(CellPortKey port) const + { + delay_t slack = std::numeric_limits::max(); + for (const auto &dp : ports.at(port).domain_pairs) + slack = std::min(slack, domain_pairs.at(dp.first).worst_setup_slack); + return slack; + } + + bool setup_only = false; + bool verbose_mode = false; + bool have_loops = false; + bool updated_domains = false; + + private: + void init_ports(); + void get_cell_delays(); + void get_route_delays(); + void topo_sort(); + void setup_port_domains(); + + void reset_times(); + + void walk_forward(); + void walk_backward(); + + void compute_slack(); + void compute_criticality(); + + void print_fmax(); + // get the N most failing endpoints for a given domain pair + std::vector get_failing_eps(domain_id_t domain_pair, int count); + // print the critical path for an endpoint and domain pair + void print_critical_path(CellPortKey endpoint, domain_id_t domain_pair); + + const DelayPair init_delay{std::numeric_limits::max(), std::numeric_limits::lowest()}; + + // Set arrival/required times if more/less than the current value + void set_arrival_time(CellPortKey target, domain_id_t domain, DelayPair arrival, int path_length, + CellPortKey prev = CellPortKey()); + void set_required_time(CellPortKey target, domain_id_t domain, DelayPair required, int path_length, + CellPortKey prev = CellPortKey()); + + // To avoid storing the domain tag structure (which could get large when considering more complex constrained tag + // cases), assign each domain an ID and use that instead + // An arrival or required time entry. Stores both the min/max delays; and the traversal to reach them for critical + // path reporting + struct ArrivReqTime + { + DelayPair value; + CellPortKey bwd_min, bwd_max; + int path_length; + }; + // Data per port-domain tuple + struct PortDomainPairData + { + delay_t setup_slack = std::numeric_limits::max(), hold_slack = std::numeric_limits::max(); + delay_t budget = std::numeric_limits::max(); + int max_path_length = 0; + float criticality = 0; + }; + + // A cell timing arc, used to cache cell timings and reduce the number of potentially-expensive Arch API calls + struct CellArc + { + + enum ArcType + { + COMBINATIONAL, + SETUP, + HOLD, + CLK_TO_Q + } type; + + IdString other_port; + DelayQuad value; + // Clock polarity, not used for combinational arcs + ClockEdge edge; + + CellArc(ArcType type, IdString other_port, DelayQuad value) + : type(type), other_port(other_port), value(value), edge(RISING_EDGE){}; + CellArc(ArcType type, IdString other_port, DelayQuad value, ClockEdge edge) + : type(type), other_port(other_port), value(value), edge(edge){}; + }; + + // Timing data for every cell port + struct PerPort + { + CellPortKey cell_port; + PortType type; + // per domain timings + dict arrival; + dict required; + dict domain_pairs; + // cell timing arcs to (outputs)/from (inputs) from this port + std::vector cell_arcs; + // routing delay into this port (input ports only) + DelayPair route_delay{0}; + // worst criticality and slack across domain pairs + float worst_crit = 0; + delay_t worst_setup_slack = std::numeric_limits::max(), + worst_hold_slack = std::numeric_limits::max(); + }; + + struct PerDomain + { + PerDomain(ClockDomainKey key) : key(key){}; + ClockDomainKey key; + // these are pairs (signal port; clock port) + std::vector> startpoints, endpoints; + }; + + struct PerDomainPair + { + PerDomainPair(ClockDomainPairKey key) : key(key){}; + ClockDomainPairKey key; + DelayPair period{0}; + delay_t worst_setup_slack, worst_hold_slack; + }; + + CellInfo *cell_info(const CellPortKey &key); + PortInfo &port_info(const CellPortKey &key); + + domain_id_t domain_id(IdString cell, IdString clock_port, ClockEdge edge); + domain_id_t domain_id(const NetInfo *net, ClockEdge edge); + domain_id_t domain_pair_id(domain_id_t launch, domain_id_t capture); + + void copy_domains(const CellPortKey &from, const CellPortKey &to, bool backwards); + + dict ports; + dict domain_to_id; + dict pair_to_id; + std::vector domains; + std::vector domain_pairs; + + std::vector topological_order; + + Context *ctx; +}; + +// Evenly redistribute the total path slack amongst all sinks on each path +void assign_budget(Context *ctx, bool quiet = false); + +// Perform timing analysis and print out the fmax, and optionally the +// critical path +void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false, + bool warn_on_failure = false, bool update_results = false); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/kernel/util.h b/common/kernel/util.h new file mode 100644 index 00000000..c10abb72 --- /dev/null +++ b/common/kernel/util.h @@ -0,0 +1,241 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * 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 UTIL_H +#define UTIL_H + +#include +#include +#include +#include "nextpnr.h" + +#include "log.h" + +NEXTPNR_NAMESPACE_BEGIN + +// Get a value from a map-style container, returning default if value is not +// found +template +ValueType get_or_default(const Container &ct, const KeyType &key, ValueType def = ValueType()) +{ + auto found = ct.find(key); + if (found == ct.end()) + return def; + else + return found->second; +}; + +// Get a value from a map-style container, returning default if value is not +// found (forces string) +template +std::string str_or_default(const Container &ct, const KeyType &key, std::string def = "") +{ + auto found = ct.find(key); + if (found == ct.end()) + return def; + else { + return found->second; + } +}; + +template +std::string str_or_default(const dict &ct, const KeyType &key, std::string def = "") +{ + auto found = ct.find(key); + if (found == ct.end()) + return def; + else { + if (!found->second.is_string) + log_error("Expecting string value but got integer %d.\n", int(found->second.intval)); + return found->second.as_string(); + } +}; + +// Get a value from a map-style container, converting to int, and returning +// default if value is not found +template int int_or_default(const Container &ct, const KeyType &key, int def = 0) +{ + auto found = ct.find(key); + if (found == ct.end()) + return def; + else + return std::stoi(found->second); +}; + +template int int_or_default(const dict &ct, const KeyType &key, int def = 0) +{ + auto found = ct.find(key); + if (found == ct.end()) + return def; + else { + if (found->second.is_string) { + try { + return std::stoi(found->second.as_string()); + } catch (std::invalid_argument &e) { + log_error("Expecting numeric value but got '%s'.\n", found->second.as_string().c_str()); + } + } else + return found->second.as_int64(); + } +}; + +// As above, but convert to bool +template +bool bool_or_default(const Container &ct, const KeyType &key, bool def = false) +{ + return bool(int_or_default(ct, key, int(def))); +}; + +// Return a net if port exists, or nullptr +inline const NetInfo *get_net_or_empty(const CellInfo *cell, const IdString port) +{ + auto found = cell->ports.find(port); + if (found != cell->ports.end()) + return found->second.net; + else + return nullptr; +} + +inline NetInfo *get_net_or_empty(CellInfo *cell, const IdString port) +{ + auto found = cell->ports.find(port); + if (found != cell->ports.end()) + return found->second.net; + else + return nullptr; +} + +// Get only value from a forward iterator begin/end pair. +// +// Generates assertion failure if std::distance(begin, end) != 1. +template +inline const typename ForwardIterator::reference get_only_value(ForwardIterator begin, ForwardIterator end) +{ + NPNR_ASSERT(begin != end); + const typename ForwardIterator::reference ret = *begin; + ++begin; + NPNR_ASSERT(begin == end); + return ret; +} + +// Get only value from a forward iterator range pair. +// +// Generates assertion failure if std::distance(r.begin(), r.end()) != 1. +template inline auto get_only_value(ForwardRange r) +{ + auto b = r.begin(); + auto e = r.end(); + return get_only_value(b, e); +} + +// From Yosys +// https://github.com/YosysHQ/yosys/blob/0fb4224ebca86156a1296b9210116d9a9cbebeed/kernel/utils.h#L131 +template > struct TopoSort +{ + bool analyze_loops, found_loops; + std::map, C> database; + std::set> loops; + std::vector sorted; + + TopoSort() + { + analyze_loops = true; + found_loops = false; + } + + void node(T n) + { + if (database.count(n) == 0) + database[n] = std::set(); + } + + void edge(T left, T right) + { + node(left); + database[right].insert(left); + } + + void sort_worker(const T &n, std::set &marked_cells, std::set &active_cells, + std::vector &active_stack) + { + if (active_cells.count(n)) { + found_loops = true; + if (analyze_loops) { + std::set loop; + for (int i = int(active_stack.size()) - 1; i >= 0; i--) { + loop.insert(active_stack[i]); + if (active_stack[i] == n) + break; + } + loops.insert(loop); + } + return; + } + + if (marked_cells.count(n)) + return; + + if (!database.at(n).empty()) { + if (analyze_loops) + active_stack.push_back(n); + active_cells.insert(n); + + for (auto &left_n : database.at(n)) + sort_worker(left_n, marked_cells, active_cells, active_stack); + + if (analyze_loops) + active_stack.pop_back(); + active_cells.erase(n); + } + + marked_cells.insert(n); + sorted.push_back(n); + } + + bool sort() + { + loops.clear(); + sorted.clear(); + found_loops = false; + + std::set marked_cells; + std::set active_cells; + std::vector active_stack; + + for (auto &it : database) + sort_worker(it.first, marked_cells, active_cells, active_stack); + + NPNR_ASSERT(sorted.size() == database.size()); + return !found_loops; + } +}; + +template struct reversed_range_t +{ + T &obj; + explicit reversed_range_t(T &obj) : obj(obj){}; + auto begin() { return obj.rbegin(); } + auto end() { return obj.rend(); } +}; + +template reversed_range_t reversed_range(T &obj) { return reversed_range_t(obj); } + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/log.cc b/common/log.cc deleted file mode 100644 index 8b1ad43b..00000000 --- a/common/log.cc +++ /dev/null @@ -1,198 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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 -#include -#include -#include -#include -#include -#include - -#include "log.h" - -NEXTPNR_NAMESPACE_BEGIN - -NPNR_NORETURN void logv_error(const char *format, va_list ap) NPNR_ATTRIBUTE(noreturn); - -std::vector> log_streams; -log_write_type log_write_function = nullptr; - -std::string log_last_error; -void (*log_error_atexit)() = NULL; - -dict message_count_by_level; -static int log_newline_count = 0; -bool had_nonfatal_error = false; - -std::string stringf(const char *fmt, ...) -{ - std::string string; - va_list ap; - - va_start(ap, fmt); - string = vstringf(fmt, ap); - va_end(ap); - - return string; -} - -std::string vstringf(const char *fmt, va_list ap) -{ - std::string string; - char *str = NULL; - -#if defined(_WIN32) || defined(__CYGWIN__) - int sz = 64 + strlen(fmt), rc; - while (1) { - va_list apc; - va_copy(apc, ap); - str = (char *)realloc(str, sz); - rc = vsnprintf(str, sz, fmt, apc); - va_end(apc); - if (rc >= 0 && rc < sz) - break; - sz *= 2; - } -#else - if (vasprintf(&str, fmt, ap) < 0) - str = NULL; -#endif - - if (str != NULL) { - string = str; - free(str); - } - - return string; -} - -void logv(const char *format, va_list ap, LogLevel level = LogLevel::LOG_MSG) -{ - // - // Trim newlines from the beginning - while (format[0] == '\n' && format[1] != 0) { - log_always("\n"); - format++; - } - - std::string str = vstringf(format, ap); - - if (str.empty()) - return; - - size_t nnl_pos = str.find_last_not_of('\n'); - if (nnl_pos == std::string::npos) - log_newline_count += str.size(); - else - log_newline_count = str.size() - nnl_pos - 1; - - for (auto f : log_streams) - if (f.second <= level) - *f.first << str; - if (log_write_function) - log_write_function(str); -} - -void log_with_level(LogLevel level, const char *format, ...) -{ - message_count_by_level[level]++; - va_list ap; - va_start(ap, format); - logv(format, ap, level); - va_end(ap); -} - -void logv_prefixed(const char *prefix, const char *format, va_list ap, LogLevel level) -{ - std::string message = vstringf(format, ap); - - log_with_level(level, "%s%s", prefix, message.c_str()); - log_flush(); -} - -void log_always(const char *format, ...) -{ - va_list ap; - va_start(ap, format); - logv(format, ap, LogLevel::ALWAYS_MSG); - va_end(ap); -} - -void log(const char *format, ...) -{ - va_list ap; - va_start(ap, format); - logv(format, ap, LogLevel::LOG_MSG); - va_end(ap); -} - -void log_info(const char *format, ...) -{ - va_list ap; - va_start(ap, format); - logv_prefixed("Info: ", format, ap, LogLevel::INFO_MSG); - va_end(ap); -} - -void log_warning(const char *format, ...) -{ - va_list ap; - va_start(ap, format); - logv_prefixed("Warning: ", format, ap, LogLevel::WARNING_MSG); - va_end(ap); -} - -void log_error(const char *format, ...) -{ - va_list ap; - va_start(ap, format); - logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG); - - if (log_error_atexit) - log_error_atexit(); - - throw log_execution_error_exception(); -} - -void log_break() -{ - if (log_newline_count < 2) - log("\n"); - if (log_newline_count < 2) - log("\n"); -} - -void log_nonfatal_error(const char *format, ...) -{ - va_list ap; - va_start(ap, format); - logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG); - va_end(ap); - had_nonfatal_error = true; -} - -void log_flush() -{ - for (auto f : log_streams) - f.first->flush(); -} - -NEXTPNR_NAMESPACE_END diff --git a/common/log.h b/common/log.h deleted file mode 100644 index 0ac4edf5..00000000 --- a/common/log.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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 LOG_H -#define LOG_H - -#include -#include -#include -#include -#include -#include -#include -#include "hashlib.h" -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -typedef std::function log_write_type; - -struct log_cmd_error_exception -{ -}; - -struct log_execution_error_exception -{ -}; - -enum class LogLevel -{ - LOG_MSG, - INFO_MSG, - WARNING_MSG, - ERROR_MSG, - ALWAYS_MSG -}; - -struct loglevel_hash_ops -{ - static inline bool cmp(LogLevel a, LogLevel b) { return a == b; } - static inline unsigned int hash(LogLevel a) { return unsigned(a); } -}; - -extern std::vector> log_streams; -extern log_write_type log_write_function; - -extern std::string log_last_error; -extern void (*log_error_atexit)(); -extern bool had_nonfatal_error; -extern dict message_count_by_level; - -std::string stringf(const char *fmt, ...); -std::string vstringf(const char *fmt, va_list ap); - -void log(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); -void log_always(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); -void log_info(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); -void log_warning(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); -NPNR_NORETURN void log_error(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2), noreturn); -void log_nonfatal_error(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2)); -void log_break(); -void log_flush(); - -static inline void log_assert_worker(bool cond, const char *expr, const char *file, int line) -{ - if (!cond) - log_error("Assert `%s' failed in %s:%d.\n", expr, file, line); -} -#define log_assert(_assert_expr_) \ - NEXTPNR_NAMESPACE_PREFIX log_assert_worker(_assert_expr_, #_assert_expr_, __FILE__, __LINE__) - -#define log_abort() log_error("Abort in %s:%d.\n", __FILE__, __LINE__) - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/nextpnr.cc b/common/nextpnr.cc deleted file mode 100644 index 8c902d88..00000000 --- a/common/nextpnr.cc +++ /dev/null @@ -1,35 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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. - * - */ - -#if defined(__wasm) -#include -#include -#include "log.h" - -extern "C" { -// FIXME: WASI does not currently support exceptions. -void *__cxa_allocate_exception(size_t thrown_size) throw() { return malloc(thrown_size); } -bool __cxa_uncaught_exception() throw(); -void __cxa_throw(void *thrown_exception, struct std::type_info *tinfo, void (*dest)(void *)) { std::terminate(); } -} - -namespace boost { -void throw_exception(std::exception const &e) { NEXTPNR_NAMESPACE::log_error("boost::exception(): %s\n", e.what()); } -} // namespace boost -#endif diff --git a/common/nextpnr.h b/common/nextpnr.h deleted file mode 100644 index 3b65900b..00000000 --- a/common/nextpnr.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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 -#define NEXTPNR_H - -#include "base_arch.h" -#include "context.h" -#include "nextpnr_assertions.h" -#include "nextpnr_namespaces.h" -#include "nextpnr_types.h" - -#endif diff --git a/common/nextpnr_assertions.cc b/common/nextpnr_assertions.cc deleted file mode 100644 index ac4cdf57..00000000 --- a/common/nextpnr_assertions.cc +++ /dev/null @@ -1,33 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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_assertions.h" -#include "log.h" - -NEXTPNR_NAMESPACE_BEGIN - -assertion_failure::assertion_failure(std::string msg, std::string expr_str, std::string filename, int line) - : runtime_error("Assertion failure: " + msg + " (" + filename + ":" + std::to_string(line) + ")"), msg(msg), - expr_str(expr_str), filename(filename), line(line) -{ - log_flush(); -} - -NEXTPNR_NAMESPACE_END diff --git a/common/nextpnr_assertions.h b/common/nextpnr_assertions.h deleted file mode 100644 index 1989aa3a..00000000 --- a/common/nextpnr_assertions.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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_ASSERTIONS_H -#define NEXTPNR_ASSERTIONS_H - -#include -#include - -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -class assertion_failure : public std::runtime_error -{ - public: - assertion_failure(std::string msg, std::string expr_str, std::string filename, int line); - - std::string msg; - std::string expr_str; - std::string filename; - int line; -}; - -NPNR_NORETURN -inline void assert_fail_impl(const char *message, const char *expr_str, const char *filename, int line) -{ - throw assertion_failure(message, expr_str, filename, line); -} - -NPNR_NORETURN -inline void assert_fail_impl_str(std::string message, const char *expr_str, const char *filename, int line) -{ - throw assertion_failure(message, expr_str, filename, line); -} - -#define NPNR_ASSERT(cond) (!(cond) ? assert_fail_impl(#cond, #cond, __FILE__, __LINE__) : (void)true) -#define NPNR_ASSERT_MSG(cond, msg) (!(cond) ? assert_fail_impl(msg, #cond, __FILE__, __LINE__) : (void)true) -#define NPNR_ASSERT_FALSE(msg) (assert_fail_impl(msg, "false", __FILE__, __LINE__)) -#define NPNR_ASSERT_FALSE_STR(msg) (assert_fail_impl_str(msg, "false", __FILE__, __LINE__)) - -#define NPNR_STRINGIFY_MACRO(x) NPNR_STRINGIFY(x) -#define NPNR_STRINGIFY(x) #x - -NEXTPNR_NAMESPACE_END - -#endif /* NEXTPNR_ASSERTIONS_H */ diff --git a/common/nextpnr_base_types.h b/common/nextpnr_base_types.h deleted file mode 100644 index 944bf0b8..00000000 --- a/common/nextpnr_base_types.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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. - * - */ - -// Theses are the nextpnr types that do **not** depend on user defined types, -// like BelId, etc. -// -// If a common type is required that depends on one of the user defined types, -// add it to nextpnr_types.h, which includes "archdefs.h", or make a new -// header that includes "archdefs.h" -#ifndef NEXTPNR_BASE_TYPES_H -#define NEXTPNR_BASE_TYPES_H - -#include -#include - -#include "hashlib.h" -#include "idstring.h" -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct GraphicElement -{ - enum type_t - { - TYPE_NONE, - TYPE_LINE, - TYPE_ARROW, - TYPE_BOX, - TYPE_CIRCLE, - TYPE_LABEL, - TYPE_LOCAL_ARROW, // Located entirely within the cell boundaries, coordinates in the range [0., 1.] - TYPE_LOCAL_LINE, - - TYPE_MAX - } type = TYPE_NONE; - - enum style_t - { - STYLE_GRID, - STYLE_FRAME, // Static "frame". Contrast between STYLE_INACTIVE and STYLE_ACTIVE - STYLE_HIDDEN, // Only display when object is selected or highlighted - STYLE_INACTIVE, // Render using low-contrast color - STYLE_ACTIVE, // Render using high-contast color - - // UI highlight groups - STYLE_HIGHLIGHTED0, - STYLE_HIGHLIGHTED1, - STYLE_HIGHLIGHTED2, - STYLE_HIGHLIGHTED3, - STYLE_HIGHLIGHTED4, - STYLE_HIGHLIGHTED5, - STYLE_HIGHLIGHTED6, - STYLE_HIGHLIGHTED7, - - STYLE_SELECTED, - STYLE_HOVER, - - STYLE_MAX - } style = STYLE_FRAME; - - float x1 = 0, y1 = 0, x2 = 0, y2 = 0, z = 0; - std::string text; - GraphicElement(){}; - GraphicElement(type_t type, style_t style, float x1, float y1, float x2, float y2, float z) - : type(type), style(style), x1(x1), y1(y1), x2(x2), y2(y2), z(z){}; -}; - -struct Loc -{ - int x = -1, y = -1, z = -1; - - Loc() {} - Loc(int x, int y, int z) : x(x), y(y), z(z) {} - - bool operator==(const Loc &other) const { return (x == other.x) && (y == other.y) && (z == other.z); } - bool operator!=(const Loc &other) const { return (x != other.x) || (y != other.y) || (z != other.z); } - unsigned int hash() const { return mkhash(x, mkhash(y, z)); } -}; - -struct ArcBounds -{ - int x0 = -1, y0 = -1, x1 = -1, y1 = -1; - - ArcBounds() {} - ArcBounds(int x0, int y0, int x1, int y1) : x0(x0), y0(y0), x1(x1), y1(y1){}; - - int distance(Loc loc) const - { - int dist = 0; - if (loc.x < x0) - dist += x0 - loc.x; - if (loc.x > x1) - dist += loc.x - x1; - if (loc.y < y0) - dist += y0 - loc.y; - if (loc.y > y1) - dist += loc.y - y1; - return dist; - }; - - bool contains(int x, int y) const { return x >= x0 && y >= y0 && x <= x1 && y <= y1; } -}; - -enum PlaceStrength -{ - STRENGTH_NONE = 0, - STRENGTH_WEAK = 1, - STRENGTH_STRONG = 2, - STRENGTH_PLACER = 3, - STRENGTH_FIXED = 4, - STRENGTH_LOCKED = 5, - STRENGTH_USER = 6 -}; - -NEXTPNR_NAMESPACE_END - -#endif /* NEXTPNR_BASE_TYPES_H */ diff --git a/common/nextpnr_namespaces.cc b/common/nextpnr_namespaces.cc deleted file mode 100644 index 802c89b4..00000000 --- a/common/nextpnr_namespaces.cc +++ /dev/null @@ -1,23 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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. - * - */ - -// This cc file exists to ensure that "nextpnr_namespaces.h" can be compiled -// on its own. -#include "nextpnr_namespaces.h" diff --git a/common/nextpnr_namespaces.h b/common/nextpnr_namespaces.h deleted file mode 100644 index b758d7c5..00000000 --- a/common/nextpnr_namespaces.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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_NAMESPACES_H -#define NEXTPNR_NAMESPACES_H - -#ifdef NEXTPNR_NAMESPACE -#define NEXTPNR_NAMESPACE_PREFIX NEXTPNR_NAMESPACE:: -#define NEXTPNR_NAMESPACE_BEGIN namespace NEXTPNR_NAMESPACE { -#define NEXTPNR_NAMESPACE_END } -#define USING_NEXTPNR_NAMESPACE using namespace NEXTPNR_NAMESPACE; -#else -#define NEXTPNR_NAMESPACE_PREFIX -#define NEXTPNR_NAMESPACE_BEGIN -#define NEXTPNR_NAMESPACE_END -#define USING_NEXTPNR_NAMESPACE -#endif - -#define NPNR_UNUSED(x) ((void)x) - -#if defined(__GNUC__) || defined(__clang__) -#define NPNR_ATTRIBUTE(...) __attribute__((__VA_ARGS__)) -#define NPNR_NORETURN __attribute__((noreturn)) -#define NPNR_DEPRECATED __attribute__((deprecated)) -#define NPNR_PACKED_STRUCT(...) __VA_ARGS__ __attribute__((packed)) -#define NPNR_ALWAYS_INLINE NPNR_ATTRIBUTE(__always_inline__) -#elif defined(_MSC_VER) -#define NPNR_ATTRIBUTE(...) -#define NPNR_NORETURN __declspec(noreturn) -#define NPNR_DEPRECATED __declspec(deprecated) -#define NPNR_PACKED_STRUCT(...) __pragma(pack(push, 1)) __VA_ARGS__ __pragma(pack(pop)) -#define NPNR_ALWAYS_INLINE -#else -#define NPNR_ATTRIBUTE(...) -#define NPNR_NORETURN -#define NPNR_DEPRECATED -#define NPNR_PACKED_STRUCT(...) __VA_ARGS__ -#define NPNR_ALWAYS_INLINE -#endif - -#endif /* NEXTPNR_NAMESPACES_H */ diff --git a/common/nextpnr_types.cc b/common/nextpnr_types.cc deleted file mode 100644 index 57d816c0..00000000 --- a/common/nextpnr_types.cc +++ /dev/null @@ -1,180 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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_types.h" -#include "context.h" -#include "log.h" - -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -void CellInfo::addInput(IdString name) -{ - ports[name].name = name; - ports[name].type = PORT_IN; -} -void CellInfo::addOutput(IdString name) -{ - ports[name].name = name; - ports[name].type = PORT_OUT; -} -void CellInfo::addInout(IdString name) -{ - ports[name].name = name; - ports[name].type = PORT_INOUT; -} - -void CellInfo::setParam(IdString name, Property value) { params[name] = value; } -void CellInfo::unsetParam(IdString name) { params.erase(name); } -void CellInfo::setAttr(IdString name, Property value) { attrs[name] = value; } -void CellInfo::unsetAttr(IdString name) { attrs.erase(name); } - -bool CellInfo::testRegion(BelId bel) const -{ - return region == nullptr || !region->constr_bels || region->bels.count(bel); -} - -void CellInfo::connectPort(IdString port_name, NetInfo *net) -{ - if (net == nullptr) - return; - PortInfo &port = ports.at(port_name); - NPNR_ASSERT(port.net == nullptr); - port.net = net; - if (port.type == PORT_OUT) { - NPNR_ASSERT(net->driver.cell == nullptr); - net->driver.cell = this; - net->driver.port = port_name; - } else if (port.type == PORT_IN || port.type == PORT_INOUT) { - PortRef user; - user.cell = this; - user.port = port_name; - port.user_idx = net->users.add(user); - } else { - NPNR_ASSERT_FALSE("invalid port type for connect_port"); - } -} - -void CellInfo::disconnectPort(IdString port_name) -{ - if (!ports.count(port_name)) - return; - PortInfo &port = ports.at(port_name); - if (port.net != nullptr) { - if (port.user_idx) - port.net->users.remove(port.user_idx); - if (port.net->driver.cell == this && port.net->driver.port == port_name) - port.net->driver.cell = nullptr; - port.net = nullptr; - } -} - -void CellInfo::connectPorts(IdString port, CellInfo *other, IdString other_port) -{ - PortInfo &port1 = ports.at(port); - if (port1.net == nullptr) { - // No net on port1; need to create one - NetInfo *p1net = ctx->createNet(ctx->id(name.str(ctx) + "$conn$" + port.str(ctx))); - connectPort(port, p1net); - } - other->connectPort(other_port, port1.net); -} - -void CellInfo::movePortTo(IdString port, CellInfo *other, IdString other_port) -{ - if (!ports.count(port)) - return; - PortInfo &old = ports.at(port); - - // Create port on the replacement cell if it doesn't already exist - if (!other->ports.count(other_port)) { - other->ports[other_port].name = other_port; - other->ports[other_port].type = old.type; - } - - PortInfo &rep = other->ports.at(other_port); - NPNR_ASSERT(old.type == rep.type); - - rep.net = old.net; - rep.user_idx = old.user_idx; - old.net = nullptr; - old.user_idx = store_index{}; - if (rep.type == PORT_OUT) { - if (rep.net != nullptr) { - rep.net->driver.cell = other; - rep.net->driver.port = other_port; - } - } else if (rep.type == PORT_IN) { - if (rep.net != nullptr) { - auto &load = rep.net->users.at(rep.user_idx); - load.cell = other; - load.port = other_port; - } - } else { - NPNR_ASSERT(false); - } -} - -void CellInfo::renamePort(IdString old_name, IdString new_name) -{ - if (!ports.count(old_name)) - return; - PortInfo pi = ports.at(old_name); - if (pi.net != nullptr) { - if (pi.net->driver.cell == this && pi.net->driver.port == old_name) - pi.net->driver.port = new_name; - if (pi.user_idx) - pi.net->users.at(pi.user_idx).port = new_name; - } - ports.erase(old_name); - pi.name = new_name; - ports[new_name] = pi; -} - -void CellInfo::movePortBusTo(IdString old_name, int old_offset, bool old_brackets, CellInfo *new_cell, - IdString new_name, int new_offset, bool new_brackets, int width) -{ - for (int i = 0; i < width; i++) { - IdString old_port = ctx->id(stringf(old_brackets ? "%s[%d]" : "%s%d", old_name.c_str(ctx), i + old_offset)); - IdString new_port = ctx->id(stringf(new_brackets ? "%s[%d]" : "%s%d", new_name.c_str(ctx), i + new_offset)); - movePortTo(old_port, new_cell, new_port); - } -} - -void CellInfo::copyPortTo(IdString port, CellInfo *other, IdString other_port) -{ - if (!ports.count(port)) - return; - other->ports[other_port].name = other_port; - other->ports[other_port].type = ports.at(port).type; - other->connectPort(other_port, ports.at(port).net); -} - -void CellInfo::copyPortBusTo(IdString old_name, int old_offset, bool old_brackets, CellInfo *new_cell, - IdString new_name, int new_offset, bool new_brackets, int width) -{ - for (int i = 0; i < width; i++) { - IdString old_port = ctx->id(stringf(old_brackets ? "%s[%d]" : "%s%d", old_name.c_str(ctx), i + old_offset)); - IdString new_port = ctx->id(stringf(new_brackets ? "%s[%d]" : "%s%d", new_name.c_str(ctx), i + new_offset)); - copyPortTo(old_port, new_cell, new_port); - } -} - -NEXTPNR_NAMESPACE_END diff --git a/common/nextpnr_types.h b/common/nextpnr_types.h deleted file mode 100644 index c21182cc..00000000 --- a/common/nextpnr_types.h +++ /dev/null @@ -1,364 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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. - * - */ - -// Types defined in this header use one or more user defined types (e.g. BelId). -// If a new common type is desired that doesn't depend on a user defined type, -// either put it in it's own header, or in nextpnr_base_types.h. -#ifndef NEXTPNR_TYPES_H -#define NEXTPNR_TYPES_H - -#include -#include - -#include "archdefs.h" -#include "hashlib.h" -#include "indexed_store.h" -#include "nextpnr_base_types.h" -#include "nextpnr_namespaces.h" -#include "property.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct DecalXY -{ - DecalId decal; - float x = 0, y = 0; - - bool operator==(const DecalXY &other) const { return (decal == other.decal && x == other.x && y == other.y); } -}; - -struct BelPin -{ - BelId bel; - IdString pin; -}; - -struct Region -{ - IdString name; - - bool constr_bels = false; - bool constr_wires = false; - bool constr_pips = false; - - pool bels; - pool wires; - pool piplocs; -}; - -struct PipMap -{ - PipId pip = PipId(); - PlaceStrength strength = STRENGTH_NONE; -}; - -struct CellInfo; - -struct PortRef -{ - CellInfo *cell = nullptr; - IdString port; - delay_t budget = 0; -}; - -// minimum and maximum delay -struct DelayPair -{ - DelayPair(){}; - explicit DelayPair(delay_t delay) : min_delay(delay), max_delay(delay){}; - DelayPair(delay_t min_delay, delay_t max_delay) : min_delay(min_delay), max_delay(max_delay){}; - delay_t minDelay() const { return min_delay; }; - delay_t maxDelay() const { return max_delay; }; - delay_t min_delay, max_delay; - DelayPair operator+(const DelayPair &other) const - { - return {min_delay + other.min_delay, max_delay + other.max_delay}; - } - DelayPair operator-(const DelayPair &other) const - { - return {min_delay - other.min_delay, max_delay - other.max_delay}; - } -}; - -// four-quadrant, min and max rise and fall delay -struct DelayQuad -{ - DelayPair rise, fall; - DelayQuad(){}; - explicit DelayQuad(delay_t delay) : rise(delay), fall(delay){}; - DelayQuad(delay_t min_delay, delay_t max_delay) : rise(min_delay, max_delay), fall(min_delay, max_delay){}; - DelayQuad(DelayPair rise, DelayPair fall) : rise(rise), fall(fall){}; - DelayQuad(delay_t min_rise, delay_t max_rise, delay_t min_fall, delay_t max_fall) - : rise(min_rise, max_rise), fall(min_fall, max_fall){}; - - delay_t minRiseDelay() const { return rise.minDelay(); }; - delay_t maxRiseDelay() const { return rise.maxDelay(); }; - delay_t minFallDelay() const { return fall.minDelay(); }; - delay_t maxFallDelay() const { return fall.maxDelay(); }; - delay_t minDelay() const { return std::min(rise.minDelay(), fall.minDelay()); }; - delay_t maxDelay() const { return std::max(rise.maxDelay(), fall.maxDelay()); }; - - DelayPair delayPair() const { return DelayPair(minDelay(), maxDelay()); }; - - DelayQuad operator+(const DelayQuad &other) const { return {rise + other.rise, fall + other.fall}; } - DelayQuad operator-(const DelayQuad &other) const { return {rise - other.rise, fall - other.fall}; } -}; - -struct ClockConstraint; - -struct NetInfo : ArchNetInfo -{ - explicit NetInfo(IdString name) : name(name){}; - IdString name, hierpath; - int32_t udata = 0; - - PortRef driver; - indexed_store users; - dict attrs; - - // wire -> uphill_pip - dict wires; - - std::vector aliases; // entries in net_aliases that point to this net - - std::unique_ptr clkconstr; - - Region *region = nullptr; -}; - -enum PortType -{ - PORT_IN = 0, - PORT_OUT = 1, - PORT_INOUT = 2 -}; - -struct PortInfo -{ - IdString name; - NetInfo *net; - PortType type; - store_index user_idx{}; -}; - -struct Context; - -struct CellInfo : ArchCellInfo -{ - CellInfo(Context *ctx, IdString name, IdString type) : ctx(ctx), name(name), type(type){}; - Context *ctx = nullptr; - - IdString name, type, hierpath; - int32_t udata; - - dict ports; - dict attrs, params; - - BelId bel; - PlaceStrength belStrength = STRENGTH_NONE; - - // cell is part of a cluster if != ClusterId - ClusterId cluster; - - Region *region = nullptr; - - void addInput(IdString name); - void addOutput(IdString name); - void addInout(IdString name); - - void setParam(IdString name, Property value); - void unsetParam(IdString name); - void setAttr(IdString name, Property value); - void unsetAttr(IdString name); - // check whether a bel complies with the cell's region constraint - bool testRegion(BelId bel) const; - - NetInfo *getPort(IdString name) - { - auto found = ports.find(name); - return (found == ports.end()) ? nullptr : found->second.net; - } - const NetInfo *getPort(IdString name) const - { - auto found = ports.find(name); - return (found == ports.end()) ? nullptr : found->second.net; - } - void connectPort(IdString port, NetInfo *net); - void disconnectPort(IdString port); - void connectPorts(IdString port, CellInfo *other, IdString other_port); - void movePortTo(IdString port, CellInfo *other, IdString other_port); - void renamePort(IdString old_name, IdString new_name); - void movePortBusTo(IdString old_name, int old_offset, bool old_brackets, CellInfo *new_cell, IdString new_name, - int new_offset, bool new_brackets, int width); - void copyPortTo(IdString port, CellInfo *other, IdString other_port); - void copyPortBusTo(IdString old_name, int old_offset, bool old_brackets, CellInfo *new_cell, IdString new_name, - int new_offset, bool new_brackets, int width); -}; - -enum TimingPortClass -{ - TMG_CLOCK_INPUT, // Clock input to a sequential cell - TMG_GEN_CLOCK, // Generated clock output (PLL, DCC, etc) - TMG_REGISTER_INPUT, // Input to a register, with an associated clock (may also have comb. fanout too) - TMG_REGISTER_OUTPUT, // Output from a register - TMG_COMB_INPUT, // Combinational input, no paths end here - TMG_COMB_OUTPUT, // Combinational output, no paths start here - TMG_STARTPOINT, // Unclocked primary startpoint, such as an IO cell output - TMG_ENDPOINT, // Unclocked primary endpoint, such as an IO cell input - TMG_IGNORE, // Asynchronous to all clocks, "don't care", and should be ignored (false path) for analysis -}; - -enum ClockEdge -{ - RISING_EDGE, - FALLING_EDGE -}; - -struct TimingClockingInfo -{ - IdString clock_port; // Port name of clock domain - ClockEdge edge; - DelayPair setup, hold; // Input timing checks - DelayQuad clockToQ; // Output clock-to-Q time -}; - -struct ClockConstraint -{ - DelayPair high; - DelayPair low; - DelayPair period; -}; - -struct ClockFmax -{ - float achieved; - float constraint; -}; - -struct ClockEvent -{ - IdString clock; - ClockEdge edge; - - bool operator==(const ClockEvent &other) const { return clock == other.clock && edge == other.edge; } - unsigned int hash() const { return mkhash(clock.hash(), int(edge)); } -}; - -struct ClockPair -{ - ClockEvent start, end; - - bool operator==(const ClockPair &other) const { return start == other.start && end == other.end; } - unsigned int hash() const { return mkhash(start.hash(), end.hash()); } -}; - -struct CriticalPath -{ - struct Segment - { - - // Segment type - enum class Type - { - CLK_TO_Q, // Clock-to-Q delay - SOURCE, // Delayless source - LOGIC, // Combinational logic delay - ROUTING, // Routing delay - SETUP // Setup time in sink - }; - - // Type - Type type; - // Net name (routing only) - IdString net; - // From cell.port - std::pair from; - // To cell.port - std::pair to; - // Segment delay - delay_t delay; - // Segment budget (routing only) - delay_t budget; - }; - - // Clock pair - ClockPair clock_pair; - // Total path delay - delay_t delay; - // Period (max allowed delay) - delay_t period; - // Individual path segments - std::vector segments; -}; - -// Holds timing information of a single source to sink path of a net -struct NetSinkTiming -{ - // Clock event pair - ClockPair clock_pair; - // Cell and port (the sink) - std::pair cell_port; - // Delay - delay_t delay; - // Delay budget - delay_t budget; -}; - -struct TimingResult -{ - // Achieved and target Fmax for all clock domains - dict clock_fmax; - // Single domain critical paths - dict clock_paths; - // Cross-domain critical paths - std::vector xclock_paths; - - // Detailed net timing data - dict> detailed_net_timings; -}; - -// Represents the contents of a non-leaf cell in a design -// with hierarchy - -struct HierarchicalPort -{ - IdString name; - PortType dir; - std::vector nets; - int offset; - bool upto; -}; - -struct HierarchicalCell -{ - IdString name, type, parent, fullpath; - // Name inside cell instance -> global name - dict leaf_cells, nets; - // Global name -> name inside cell instance - dict leaf_cells_by_gname, nets_by_gname; - // Cell port to net - dict ports; - // Name inside cell instance -> global name - dict hier_cells; -}; - -NEXTPNR_NAMESPACE_END - -#endif /* NEXTPNR_TYPES_H */ diff --git a/common/parallel_refine.cc b/common/parallel_refine.cc deleted file mode 100644 index a868ca58..00000000 --- a/common/parallel_refine.cc +++ /dev/null @@ -1,959 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021-22 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 "parallel_refine.h" -#include "log.h" - -#if !defined(__wasm) - -#include "fast_bels.h" -#include "scope_lock.h" -#include "timing.h" - -#include -#include -#include -#include -#include - -NEXTPNR_NAMESPACE_BEGIN - -namespace { -struct Partition -{ - int x0, y0, x1, y1; - std::vector cells; - Partition() = default; - explicit Partition(Context *ctx) - { - x0 = ctx->getGridDimX(); - y0 = ctx->getGridDimY(); - x1 = 0; - y1 = 0; - for (auto &cell : ctx->cells) { - Loc l = ctx->getBelLocation(cell.second->bel); - x0 = std::min(x0, l.x); - x1 = std::max(x1, l.x); - y0 = std::min(y0, l.y); - y1 = std::max(y1, l.y); - cells.push_back(cell.second.get()); - } - } - void split(Context *ctx, bool yaxis, float pivot, Partition &l, Partition &r) - { - std::sort(cells.begin(), cells.end(), [&](CellInfo *a, CellInfo *b) { - Loc l0 = ctx->getBelLocation(a->bel), l1 = ctx->getBelLocation(b->bel); - return yaxis ? (l0.y < l1.y) : (l0.x < l1.x); - }); - size_t pivot_point = size_t(cells.size() * pivot); - l.cells.clear(); - r.cells.clear(); - l.cells.reserve(pivot_point); - r.cells.reserve(cells.size() - pivot_point); - int pivot_coord = (pivot_point == 0) ? (yaxis ? y1 : x1) - : (yaxis ? ctx->getBelLocation(cells.at(pivot_point - 1)->bel).y - : ctx->getBelLocation(cells.at(pivot_point - 1)->bel).x); - for (size_t i = 0; i < cells.size(); i++) { - Loc loc = ctx->getBelLocation(cells.at(i)->bel); - ((yaxis ? loc.y : loc.x) <= pivot_coord ? l.cells : r.cells).push_back(cells.at(i)); - } - if (yaxis) { - l.x0 = r.x0 = x0; - l.x1 = r.x1 = x1; - l.y0 = y0; - l.y1 = pivot_coord; - r.y0 = (pivot_coord == y1) ? y1 : (pivot_coord + 1); - r.y1 = y1; - } else { - l.y0 = r.y0 = y0; - l.y1 = r.y1 = y1; - l.x0 = x0; - l.x1 = pivot_coord; - r.x0 = (pivot_coord == x1) ? x1 : (pivot_coord + 1); - r.x1 = x1; - } - } -}; - -typedef int64_t wirelen_t; - -struct NetBB -{ - // Actual bounding box - int x0 = 0, x1 = 0, y0 = 0, y1 = 0; - // Number of cells at each extremity - int nx0 = 0, nx1 = 0, ny0 = 0, ny1 = 0; - wirelen_t hpwl(const ParallelRefineCfg &cfg) const - { - return wirelen_t(cfg.hpwl_scale_x * (x1 - x0) + cfg.hpwl_scale_y * (y1 - y0)); - } - static NetBB compute(const Context *ctx, const NetInfo *net, const dict *cell2bel = nullptr) - { - NetBB result{}; - if (!net->driver.cell) - return result; - auto bel_loc = [&](const CellInfo *cell) { - BelId bel = cell2bel ? cell2bel->at(cell->name) : cell->bel; - return ctx->getBelLocation(bel); - }; - result.nx0 = result.nx1 = result.ny0 = result.ny1 = 1; - Loc drv_loc = bel_loc(net->driver.cell); - result.x0 = result.x1 = drv_loc.x; - result.y0 = result.y1 = drv_loc.y; - for (auto &usr : net->users) { - Loc l = bel_loc(usr.cell); - if (l.x == result.x0) - ++result.nx0; // on the edge - else if (l.x < result.x0) { - result.x0 = l.x; // extends the edge - result.nx0 = 1; - } - if (l.x == result.x1) - ++result.nx1; // on the edge - else if (l.x > result.x1) { - result.x1 = l.x; // extends the edge - result.nx1 = 1; - } - if (l.y == result.y0) - ++result.ny0; // on the edge - else if (l.y < result.y0) { - result.y0 = l.y; // extends the edge - result.ny0 = 1; - } - if (l.y == result.y1) - ++result.ny1; // on the edge - else if (l.y > result.y1) { - result.y1 = l.y; // extends the edge - result.ny1 = 1; - } - } - return result; - } -}; - -struct GlobalState -{ - explicit GlobalState(Context *ctx, ParallelRefineCfg cfg) : ctx(ctx), cfg(cfg), bels(ctx, false, 64), tmg(ctx){}; - Context *ctx; - ParallelRefineCfg cfg; - FastBels bels; - std::vector flat_nets; // flat array of all nets in the design for fast referencing by index - std::vector last_bounds; - std::vector> last_tmg_costs; - dict region_bounds; - TimingAnalyser tmg; - - std::shared_timed_mutex archapi_mutex; - - double temperature = 1e-7; - int radius = 3; - - wirelen_t total_wirelen = 0; - double total_timing_cost = 0; - - double get_timing_cost(const NetInfo *net, store_index user, - const dict *cell2bel = nullptr) - { - if (!net->driver.cell) - return 0; - const auto &sink = net->users.at(user); - IdString driver_pin, sink_pin; - // Pick the first pin for a prediction; assume all will be similar enouhg - for (auto pin : ctx->getBelPinsForCellPin(net->driver.cell, net->driver.port)) { - driver_pin = pin; - break; - } - for (auto pin : ctx->getBelPinsForCellPin(sink.cell, sink.port)) { - sink_pin = pin; - break; - } - float crit = tmg.get_criticality(CellPortKey(sink)); - BelId src_bel = cell2bel ? cell2bel->at(net->driver.cell->name) : net->driver.cell->bel; - BelId dst_bel = cell2bel ? cell2bel->at(sink.cell->name) : sink.cell->bel; - double delay = ctx->getDelayNS(ctx->predictDelay(src_bel, driver_pin, dst_bel, sink_pin)); - return delay * std::pow(crit, cfg.crit_exp); - } - - bool skip_net(const NetInfo *net) const - { - if (!net->driver.cell) - return true; - if (ctx->getBelGlobalBuf(net->driver.cell->bel)) - return true; - return false; - } - bool timing_skip_net(const NetInfo *net) const - { - if (!net->driver.cell) - return true; - int cc; - auto cls = ctx->getPortTimingClass(net->driver.cell, net->driver.port, cc); - if (cls == TMG_IGNORE || cls == TMG_GEN_CLOCK) - return true; - return false; - } - // .... -}; - -struct ThreadState -{ - Context *ctx; // Nextpnr context pointer - GlobalState &g; // Refinement engine state - int idx; // Index of the thread - DeterministicRNG rng; // Local RNG - // The cell partition that the thread works on - Partition p; - // Mapping from design-wide net index to thread-wide net index -- not all nets are in all partitions, so we can - // optimise - std::vector thread_net_idx; - // List of nets inside the partition; and their committed bounding boxes & timing costs from the thread's - // perspective - std::vector thread_nets; - std::vector net_bounds; - std::vector> arc_tmg_cost; - std::vector ignored_nets, tmg_ignored_nets; - bool arch_state_dirty = false; - // Our local cell-bel map; that won't be affected by out-of-partition moves - dict local_cell2bel; - - // Data on an inflight move - dict> moved_cells; // cell -> (old; new) - // For cluster moves only - std::vector> cell_rel; - // For incremental wirelength and delay updates - wirelen_t wirelen_delta = 0; - double timing_delta = 0; - - // Total made and accepted moved - int n_move = 0, n_accept = 0; - - enum BoundChange - { - NO_CHANGE, - CELL_MOVED_INWARDS, - CELL_MOVED_OUTWARDS, - FULL_RECOMPUTE - }; - // Wirelen related are handled on a per-axis basis to reduce - struct AxisChanges - { - std::vector bounds_changed_nets; - std::vector already_bounds_changed; - }; - std::array axes; - std::vector new_net_bounds; - - std::vector> already_timing_changed; - std::vector>> timing_changed_arcs; - std::vector new_timing_costs; - - ThreadState(Context *ctx, GlobalState &g, int idx) : ctx(ctx), g(g), idx(idx){}; - void set_partition(const Partition &part) - { - p = part; - thread_nets.clear(); - thread_net_idx.resize(g.flat_nets.size()); - std::fill(thread_net_idx.begin(), thread_net_idx.end(), -1); - // Determine the set of nets that are within the thread; and therefore we care about - for (auto thread_cell : part.cells) { - for (auto &port : thread_cell->ports) { - if (!port.second.net) - continue; - int global_idx = port.second.net->udata; - auto &thread_idx = thread_net_idx.at(global_idx); - // Already added to the set - if (thread_idx != -1) - continue; - thread_idx = thread_nets.size(); - thread_nets.push_back(port.second.net); - } - } - tmg_ignored_nets.clear(); - ignored_nets.clear(); - for (auto tn : thread_nets) { - ignored_nets.push_back(g.skip_net(tn)); - tmg_ignored_nets.push_back(g.timing_skip_net(tn)); - } - // Set up the original cell-bel map for all nets inside the thread - local_cell2bel.clear(); - for (NetInfo *net : thread_nets) { - if (net->driver.cell) - local_cell2bel[net->driver.cell->name] = net->driver.cell->bel; - for (auto &usr : net->users) - local_cell2bel[usr.cell->name] = usr.cell->bel; - } - } - void setup_initial_state() - { - // Setup initial net bounding boxes and timing costs - net_bounds.clear(); - arc_tmg_cost.clear(); - for (auto tn : thread_nets) { - net_bounds.push_back(g.last_bounds.at(tn->udata)); - arc_tmg_cost.push_back(g.last_tmg_costs.at(tn->udata)); - } - new_net_bounds = net_bounds; - for (int j = 0; j < 2; j++) { - auto &a = axes.at(j); - a.already_bounds_changed.resize(net_bounds.size()); - } - already_timing_changed.clear(); - already_timing_changed.resize(net_bounds.size()); - for (size_t i = 0; i < thread_nets.size(); i++) - already_timing_changed.at(i) = std::vector(thread_nets.at(i)->users.capacity()); - } - bool bounds_check(BelId bel) - { - Loc l = ctx->getBelLocation(bel); - if (l.x < p.x0 || l.x > p.x1 || l.y < p.y0 || l.y > p.y1) - return false; - return true; - } - bool bind_move() - { - std::unique_lock l(g.archapi_mutex); - for (auto &entry : moved_cells) { - ctx->unbindBel(entry.second.first); - } - bool success = true; - for (auto &entry : moved_cells) { - // Make sure targets are available before we bind them - if (!ctx->checkBelAvail(entry.second.second)) { - success = false; - break; - } - ctx->bindBel(entry.second.second, ctx->cells.at(entry.first).get(), STRENGTH_WEAK); - } - arch_state_dirty = true; - return success; - } - bool check_validity() - { - std::shared_lock l(g.archapi_mutex); - bool result = true; - for (auto e : moved_cells) { - if (!ctx->isBelLocationValid(e.second.first)) { - // Have to check old; too; as unbinding a bel could make a placement illegal by virtue of no longer - // enabling dedicated routes to be used - result = false; - break; - } - if (!ctx->isBelLocationValid(e.second.second)) { - result = false; - break; - } - } - return result; - } - void revert_move() - { - if (arch_state_dirty) { - // If changes to the arch state were made, revert them by restoring original cell bindings - std::unique_lock l(g.archapi_mutex); - for (auto &entry : moved_cells) { - BelId curr_bound = ctx->cells.at(entry.first)->bel; - if (curr_bound != BelId()) - ctx->unbindBel(curr_bound); - } - for (auto &entry : moved_cells) { - ctx->bindBel(entry.second.first, ctx->cells.at(entry.first).get(), STRENGTH_WEAK); - } - arch_state_dirty = false; - } - for (auto &entry : moved_cells) - local_cell2bel[entry.first] = entry.second.first; - } - void commit_move() - { - arch_state_dirty = false; - for (auto &axis : axes) { - for (auto bc : axis.bounds_changed_nets) { - // Commit updated net bounds - net_bounds.at(bc) = new_net_bounds.at(bc); - } - } - if (g.cfg.timing_driven) { - NPNR_ASSERT(timing_changed_arcs.size() == new_timing_costs.size()); - for (size_t i = 0; i < timing_changed_arcs.size(); i++) { - auto arc = timing_changed_arcs.at(i); - arc_tmg_cost.at(arc.first).at(arc.second.idx()) = new_timing_costs.at(i); - } - } - } - void compute_changes_for_cell(CellInfo *cell, BelId old_bel, BelId new_bel) - { - Loc new_loc = ctx->getBelLocation(new_bel); - Loc old_loc = ctx->getBelLocation(old_bel); - for (const auto &port : cell->ports) { - NetInfo *pn = port.second.net; - if (!pn) - continue; - int idx = thread_net_idx.at(pn->udata); - if (ignored_nets.at(idx)) - continue; - NetBB &new_bounds = new_net_bounds.at(idx); - // For the x-axis (i=0) and y-axis (i=1) - for (int i = 0; i < 2; i++) { - auto &axis = axes.at(i); - // New and old on this axis - int new_pos = i ? new_loc.y : new_loc.x, old_pos = i ? old_loc.y : old_loc.x; - // References to updated bounding box entries - auto &b0 = i ? new_bounds.y0 : new_bounds.x0; - auto &n0 = i ? new_bounds.ny0 : new_bounds.nx0; - auto &b1 = i ? new_bounds.y1 : new_bounds.x1; - auto &n1 = i ? new_bounds.ny1 : new_bounds.nx1; - auto &change = axis.already_bounds_changed.at(idx); - // Lower bound - if (new_pos < b0) { - // Further out than current lower bound - b0 = new_pos; - n0 = 1; - if (change == NO_CHANGE) { - change = CELL_MOVED_OUTWARDS; - axis.bounds_changed_nets.push_back(idx); - } - } else if (new_pos == b0 && old_pos > b0) { - // Moved from inside into current bound - ++n0; - if (change == NO_CHANGE) { - change = CELL_MOVED_OUTWARDS; - axis.bounds_changed_nets.push_back(idx); - } - } else if (old_pos == b0 && new_pos > b0) { - // Moved from current bound to inside - if (change == NO_CHANGE) - axis.bounds_changed_nets.push_back(idx); - if (n0 == 1) { - // Was the last cell on the bound; have to do a full recompute - change = FULL_RECOMPUTE; - } else { - --n0; - if (change == NO_CHANGE) - change = CELL_MOVED_INWARDS; - } - } - // Upper bound - if (new_pos > b1) { - // Further out than current upper bound - b1 = new_pos; - n1 = new_pos; - if (change == NO_CHANGE) { - change = CELL_MOVED_OUTWARDS; - axis.bounds_changed_nets.push_back(idx); - } - } else if (new_pos == b1 && old_pos < b1) { - // Moved onto current bound - ++n1; - if (change == NO_CHANGE) { - change = CELL_MOVED_OUTWARDS; - axis.bounds_changed_nets.push_back(idx); - } - } else if (old_pos == b1 && new_pos < b1) { - // Moved from current bound to inside - if (change == NO_CHANGE) - axis.bounds_changed_nets.push_back(idx); - if (n1 == 1) { - // Was the last cell on the bound; have to do a full recompute - change = FULL_RECOMPUTE; - } else { - --n1; - if (change == NO_CHANGE) - change = CELL_MOVED_INWARDS; - } - } - } - // Timing updates if timing driven - if (g.cfg.timing_driven && !tmg_ignored_nets.at(idx)) { - if (port.second.type == PORT_OUT) { - int cc; - TimingPortClass cls = ctx->getPortTimingClass(cell, port.first, cc); - if (cls != TMG_IGNORE) { - for (auto usr : pn->users.enumerate()) - if (!already_timing_changed.at(idx).at(usr.index.idx())) { - timing_changed_arcs.emplace_back(std::make_pair(idx, usr.index)); - already_timing_changed.at(idx).at(usr.index.idx()) = true; - } - } - } else { - auto usr = port.second.user_idx; - if (!already_timing_changed.at(idx).at(usr.idx())) { - timing_changed_arcs.emplace_back(std::make_pair(idx, usr)); - already_timing_changed.at(idx).at(usr.idx()) = true; - } - } - } - } - } - void compute_total_change() - { - auto &xa = axes.at(0), &ya = axes.at(1); - for (auto &bc : xa.bounds_changed_nets) - if (xa.already_bounds_changed.at(bc) == FULL_RECOMPUTE) - new_net_bounds.at(bc) = NetBB::compute(ctx, thread_nets.at(bc), &local_cell2bel); - for (auto &bc : ya.bounds_changed_nets) - if (xa.already_bounds_changed.at(bc) != FULL_RECOMPUTE && - ya.already_bounds_changed.at(bc) == FULL_RECOMPUTE) - new_net_bounds.at(bc) = NetBB::compute(ctx, thread_nets.at(bc), &local_cell2bel); - for (auto &bc : xa.bounds_changed_nets) - wirelen_delta += (new_net_bounds.at(bc).hpwl(g.cfg) - net_bounds.at(bc).hpwl(g.cfg)); - for (auto &bc : ya.bounds_changed_nets) - if (xa.already_bounds_changed.at(bc) == NO_CHANGE) - wirelen_delta += (new_net_bounds.at(bc).hpwl(g.cfg) - net_bounds.at(bc).hpwl(g.cfg)); - if (g.cfg.timing_driven) { - NPNR_ASSERT(new_timing_costs.empty()); - for (auto arc : timing_changed_arcs) { - double new_cost = g.get_timing_cost(thread_nets.at(arc.first), arc.second, &local_cell2bel); - timing_delta += (new_cost - arc_tmg_cost.at(arc.first).at(arc.second.idx())); - new_timing_costs.push_back(new_cost); - } - } - } - void reset_move_state() - { - moved_cells.clear(); - cell_rel.clear(); - for (auto &axis : axes) { - for (auto bc : axis.bounds_changed_nets) { - new_net_bounds.at(bc) = net_bounds.at(bc); - axis.already_bounds_changed[bc] = NO_CHANGE; - } - axis.bounds_changed_nets.clear(); - } - for (auto &arc : timing_changed_arcs) { - already_timing_changed.at(arc.first).at(arc.second.idx()) = false; - } - timing_changed_arcs.clear(); - new_timing_costs.clear(); - wirelen_delta = 0; - timing_delta = 0; - } - - bool accept_move() - { - static constexpr double epsilon = 1e-20; - double delta = g.cfg.lambda * (timing_delta / std::max(epsilon, g.total_timing_cost)) + - (1.0 - g.cfg.lambda) * (double(wirelen_delta) / std::max(epsilon, g.total_wirelen)); - return delta < 0 || - (g.temperature > 1e-8 && (rng.rng() / float(0x3fffffff)) <= std::exp(-delta / g.temperature)); - } - - bool add_to_move(CellInfo *cell, BelId old_bel, BelId new_bel) - { - if (!bounds_check(old_bel) || !bounds_check(new_bel)) - return false; - if (!ctx->isValidBelForCellType(cell->type, new_bel)) - return false; - NPNR_ASSERT(!moved_cells.count(cell->name)); - moved_cells[cell->name] = std::make_pair(old_bel, new_bel); - local_cell2bel[cell->name] = new_bel; - compute_changes_for_cell(cell, old_bel, new_bel); - return true; - } - - bool single_cell_swap(CellInfo *cell, BelId new_bel) - { - NPNR_ASSERT(moved_cells.empty()); - BelId old_bel = cell->bel; - CellInfo *bound = ctx->getBoundBelCell(new_bel); - if (bound && (bound->belStrength > STRENGTH_STRONG || bound->cluster != ClusterId())) - return false; - if (!add_to_move(cell, old_bel, new_bel)) - goto fail; - if (bound && !add_to_move(bound, new_bel, old_bel)) - goto fail; - compute_total_change(); - // SA acceptance criteria - - if (!accept_move()) { - // SA fail - goto fail; - } - // Check validity rules - if (!bind_move()) - goto fail; - if (!check_validity()) - goto fail; - // Accepted! - commit_move(); - reset_move_state(); - return true; - fail: - revert_move(); - reset_move_state(); - return false; - } - - bool chain_swap(CellInfo *root_cell, BelId new_root_bel) - { - NPNR_ASSERT(moved_cells.empty()); - std::queue> displaced_clusters; - pool used_bels; - displaced_clusters.emplace(root_cell->cluster, new_root_bel); - while (!displaced_clusters.empty()) { - std::vector> dest_bels; - auto cursor = displaced_clusters.front(); - displaced_clusters.pop(); - if (!ctx->getClusterPlacement(cursor.first, cursor.second, dest_bels)) - goto fail; - for (const auto &db : dest_bels) { - BelId old_bel = db.first->bel; - if (moved_cells.count(db.first->name)) - goto fail; - if (!add_to_move(db.first, old_bel, db.second)) - goto fail; - if (used_bels.count(db.second)) - goto fail; - used_bels.insert(db.second); - - CellInfo *bound = ctx->getBoundBelCell(db.second); - if (bound) { - if (moved_cells.count(bound->name)) { - // Don't move a cell multiple times in the same go - goto fail; - } else if (bound->belStrength > STRENGTH_STRONG) { - goto fail; - } else if (bound->cluster != ClusterId()) { - // Displace the entire cluster - Loc old_loc = ctx->getBelLocation(old_bel); - Loc bound_loc = ctx->getBelLocation(bound->bel); - Loc root_loc = ctx->getBelLocation(ctx->getClusterRootCell(bound->cluster)->bel); - BelId new_root = ctx->getBelByLocation(Loc(old_loc.x + (root_loc.x - bound_loc.x), - old_loc.y + (root_loc.y - bound_loc.y), - old_loc.z + (root_loc.z - bound_loc.z))); - if (new_root == BelId()) - goto fail; - displaced_clusters.emplace(bound->cluster, new_root); - } else { - // Single cell swap - if (used_bels.count(old_bel)) - goto fail; - used_bels.insert(old_bel); - if (!add_to_move(bound, bound->bel, old_bel)) - goto fail; - } - } else if (!ctx->checkBelAvail(db.second)) { - goto fail; - } - } - } - compute_total_change(); - // SA acceptance criteria - - if (!accept_move()) { - // SA fail - goto fail; - } - // Check validity rules - if (!bind_move()) - goto fail; - if (!check_validity()) - goto fail; - // Accepted! - commit_move(); - reset_move_state(); - return true; - fail: - revert_move(); - reset_move_state(); - return false; - } - - BelId random_bel_for_cell(CellInfo *cell, int force_z = -1) - { - IdString targetType = cell->type; - Loc curr_loc = ctx->getBelLocation(cell->bel); - int count = 0; - - int dx = g.radius, dy = g.radius; - if (cell->region != nullptr && cell->region->constr_bels) { - dx = std::min(g.cfg.hpwl_scale_x * g.radius, - (g.region_bounds[cell->region->name].x1 - g.region_bounds[cell->region->name].x0) + 1); - dy = std::min(g.cfg.hpwl_scale_y * g.radius, - (g.region_bounds[cell->region->name].y1 - g.region_bounds[cell->region->name].y0) + 1); - // Clamp location to within bounds - curr_loc.x = std::max(g.region_bounds[cell->region->name].x0, curr_loc.x); - curr_loc.x = std::min(g.region_bounds[cell->region->name].x1, curr_loc.x); - curr_loc.y = std::max(g.region_bounds[cell->region->name].y0, curr_loc.y); - curr_loc.y = std::min(g.region_bounds[cell->region->name].y1, curr_loc.y); - } - - FastBels::FastBelsData *bel_data; - auto type_cnt = g.bels.getBelsForCellType(targetType, &bel_data); - - while (true) { - int nx = rng.rng(2 * dx + 1) + std::max(curr_loc.x - dx, 0); - int ny = rng.rng(2 * dy + 1) + std::max(curr_loc.y - dy, 0); - if (type_cnt < 64) - nx = ny = 0; - if (nx >= int(bel_data->size())) - continue; - if (ny >= int(bel_data->at(nx).size())) - continue; - const auto &fb = bel_data->at(nx).at(ny); - if (fb.size() == 0) - continue; - BelId bel = fb.at(rng.rng(int(fb.size()))); - if (!bounds_check(bel)) - continue; - if (force_z != -1) { - Loc loc = ctx->getBelLocation(bel); - if (loc.z != force_z) - continue; - } - if (!cell->testRegion(bel)) - continue; - count++; - return bel; - } - } - - void run_iter() - { - setup_initial_state(); - n_accept = 0; - n_move = 0; - for (int m = 0; m < g.cfg.inner_iters; m++) { - for (auto cell : p.cells) { - if (cell->belStrength > STRENGTH_STRONG) - continue; - if (cell->cluster != ClusterId()) { - if (cell != ctx->getClusterRootCell(cell->cluster)) - continue; // only move cluster root - Loc old_loc = ctx->getBelLocation(cell->bel); - BelId new_root = random_bel_for_cell(cell, old_loc.z); - if (new_root == BelId() || new_root == cell->bel) - continue; - ++n_move; - if (chain_swap(cell, new_root)) - ++n_accept; - } else { - BelId new_bel = random_bel_for_cell(cell); - if (new_bel == BelId() || new_bel == cell->bel) - continue; - ++n_move; - if (single_cell_swap(cell, new_bel)) - ++n_accept; - } - } - } - } -}; - -struct ParallelRefine -{ - Context *ctx; - GlobalState g; - std::vector t; - ParallelRefine(Context *ctx, ParallelRefineCfg cfg) : ctx(ctx), g(ctx, cfg) - { - g.flat_nets.reserve(ctx->nets.size()); - for (auto &net : ctx->nets) { - net.second->udata = g.flat_nets.size(); - g.flat_nets.push_back(net.second.get()); - } - // Setup per thread context - for (int i = 0; i < cfg.threads; i++) { - t.emplace_back(ctx, g, i); - } - // Setup region bounds - for (auto ®ion : ctx->region) { - Region *r = region.second.get(); - NetBB bb; - if (r->constr_bels) { - bb.x0 = std::numeric_limits::max(); - bb.x1 = std::numeric_limits::min(); - bb.y0 = std::numeric_limits::max(); - bb.y1 = std::numeric_limits::min(); - for (auto bel : r->bels) { - Loc loc = ctx->getBelLocation(bel); - bb.x0 = std::min(bb.x0, loc.x); - bb.x1 = std::max(bb.x1, loc.x); - bb.y0 = std::min(bb.y0, loc.y); - bb.y1 = std::max(bb.y1, loc.y); - } - } else { - bb.x0 = 0; - bb.y0 = 0; - bb.x1 = ctx->getGridDimX(); - bb.y1 = ctx->getGridDimY(); - } - g.region_bounds[r->name] = bb; - } - // Setup fast bels map - pool cell_types_in_use; - for (auto &cell : ctx->cells) { - IdString cell_type = cell.second->type; - cell_types_in_use.insert(cell_type); - } - - for (auto cell_type : cell_types_in_use) { - g.bels.addCellType(cell_type); - } - }; - std::vector parts; - void do_partition() - { - parts.clear(); - parts.emplace_back(ctx); - bool yaxis = false; - while (parts.size() < t.size()) { - std::vector next(parts.size() * 2); - for (size_t i = 0; i < parts.size(); i++) { - // Randomly permute pivot every iteration so we get different thread boundaries - const float delta = 0.1; - float pivot = (0.5 - (delta / 2)) + (delta / 2) * (ctx->rng(10000) / 10000.0f); - parts.at(i).split(ctx, yaxis, pivot, next.at(i * 2), next.at(i * 2 + 1)); - } - std::swap(parts, next); - yaxis = !yaxis; - } - - NPNR_ASSERT(parts.size() == t.size()); - // TODO: thread pool to make this worthwhile... - std::vector workers; - for (size_t i = 0; i < t.size(); i++) { - workers.emplace_back([i, this]() { t.at(i).set_partition(parts.at(i)); }); - } - for (auto &w : workers) - w.join(); - } - - void update_global_costs() - { - g.last_bounds.resize(g.flat_nets.size()); - g.last_tmg_costs.resize(g.flat_nets.size()); - g.total_wirelen = 0; - g.total_timing_cost = 0; - for (size_t i = 0; i < g.flat_nets.size(); i++) { - NetInfo *ni = g.flat_nets.at(i); - if (g.skip_net(ni)) - continue; - g.last_bounds.at(i) = NetBB::compute(ctx, ni); - g.total_wirelen += g.last_bounds.at(i).hpwl(g.cfg); - if (!g.timing_skip_net(ni)) { - auto &tc = g.last_tmg_costs.at(i); - tc.resize(ni->users.capacity()); - for (auto usr : ni->users.enumerate()) { - tc.at(usr.index.idx()) = g.get_timing_cost(ni, usr.index); - g.total_timing_cost += tc.at(usr.index.idx()); - } - } - } - } - void run() - { - - ScopeLock lock(ctx); - auto refine_start = std::chrono::high_resolution_clock::now(); - - g.tmg.setup_only = true; - g.tmg.setup(); - do_partition(); - log_info("Running parallel refinement with %d threads.\n", int(t.size())); - int iter = 1; - bool done = false; - update_global_costs(); - double avg_wirelen = g.total_wirelen; - wirelen_t min_wirelen = g.total_wirelen; - while (true) { - if (iter > 1) { - if (g.total_wirelen >= min_wirelen) { - done = true; - } else if (g.total_wirelen < min_wirelen) { - min_wirelen = g.total_wirelen; - } - int n_accept = 0, n_move = 0; - for (auto &t_data : t) { - n_accept += t_data.n_accept; - n_move += t_data.n_move; - } - double r_accept = n_accept / double(n_move); - if (g.total_wirelen < (0.95 * avg_wirelen) && g.total_wirelen > 0) { - avg_wirelen = 0.8 * avg_wirelen + 0.2 * g.total_wirelen; - } else { - if (r_accept > 0.15 && g.radius > 1) { - g.temperature *= 0.95; - } else { - g.temperature *= 0.8; - } - } - if ((iter % 10) == 0 && g.radius > 1) - --g.radius; - } - - if ((iter == 1) || ((iter % 5) == 0) || done) - log_info(" at iteration #%d: temp = %f, timing cost = " - "%.0f, wirelen = %.0f\n", - iter, g.temperature, double(g.total_timing_cost), double(g.total_wirelen)); - - if (done) - break; - - do_partition(); - - std::vector workers; - workers.reserve(t.size()); - for (int j = 0; j < int(t.size()); j++) - workers.emplace_back([this, j]() { t.at(j).run_iter(); }); - for (auto &w : workers) - w.join(); - g.tmg.run(); - update_global_costs(); - iter++; - ctx->yield(); - } - auto refine_end = std::chrono::high_resolution_clock::now(); - log_info("Placement refine time %.02fs\n", std::chrono::duration(refine_end - refine_start).count()); - } -}; -} // namespace - -ParallelRefineCfg::ParallelRefineCfg(Context *ctx) -{ - timing_driven = ctx->setting("timing_driven"); - threads = ctx->setting("threads", 8); - // snap to nearest power of two; and minimum thread size - int actual_threads = 1; - while ((actual_threads * 2) <= threads && (int(ctx->cells.size()) / (actual_threads * 2)) >= min_thread_size) - actual_threads *= 2; - threads = actual_threads; - hpwl_scale_x = 1; - hpwl_scale_y = 1; -} - -bool parallel_refine(Context *ctx, ParallelRefineCfg cfg) -{ - // TODO - ParallelRefine refine(ctx, cfg); - refine.run(); - timing_analysis(ctx); - return true; -} - -NEXTPNR_NAMESPACE_END - -#else /* !defined(__wasm) */ - -NEXTPNR_NAMESPACE_BEGIN - -bool parallel_refine(Context *ctx, ParallelRefineCfg cfg) { log_abort(); } - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/parallel_refine.h b/common/parallel_refine.h deleted file mode 100644 index 556317cd..00000000 --- a/common/parallel_refine.h +++ /dev/null @@ -1,43 +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. - * - */ - -#ifndef PARALLEL_REFINE_H -#define PARALLEL_REFINE_H - -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct ParallelRefineCfg -{ - ParallelRefineCfg(Context *ctx); - bool timing_driven; - int threads; - int hpwl_scale_x, hpwl_scale_y; - double lambda = 0.5f; - float crit_exp = 8; - int inner_iters = 15; - int min_thread_size = 500; -}; - -bool parallel_refine(Context *ctx, ParallelRefineCfg cfg); - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/place/fast_bels.h b/common/place/fast_bels.h new file mode 100644 index 00000000..ba9938c6 --- /dev/null +++ b/common/place/fast_bels.h @@ -0,0 +1,188 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * 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. + * + */ + +#pragma once + +#include +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +// FastBels is a lookup class that provides a fast lookup for finding BELs +// that support a given cell type. +struct FastBels +{ + struct TypeData + { + size_t type_index; + int number_of_possible_bels; + }; + + FastBels(Context *ctx, bool check_bel_available, int minBelsForGridPick) + : ctx(ctx), check_bel_available(check_bel_available), minBelsForGridPick(minBelsForGridPick) + { + } + + void addCellType(IdString cell_type) + { + auto iter = cell_types.find(cell_type); + if (iter != cell_types.end()) { + // This cell type has already been added to the fast BEL lookup. + return; + } + + size_t type_idx = cell_types.size(); + auto &cell_type_data = cell_types[cell_type]; + cell_type_data.type_index = type_idx; + + fast_bels_by_cell_type.resize(type_idx + 1); + auto &bel_data = fast_bels_by_cell_type.at(type_idx); + NPNR_ASSERT(bel_data.get() == nullptr); + bel_data = std::make_unique(); + + for (auto bel : ctx->getBels()) { + if (!ctx->isValidBelForCellType(cell_type, bel)) { + continue; + } + + cell_type_data.number_of_possible_bels += 1; + } + + for (auto bel : ctx->getBels()) { + if (check_bel_available && !ctx->checkBelAvail(bel)) { + continue; + } + + if (!ctx->isValidBelForCellType(cell_type, bel)) { + continue; + } + + Loc loc = ctx->getBelLocation(bel); + if (minBelsForGridPick >= 0 && cell_type_data.number_of_possible_bels < minBelsForGridPick) { + loc.x = loc.y = 0; + } + + if (int(bel_data->size()) < (loc.x + 1)) { + bel_data->resize(loc.x + 1); + } + + if (int(bel_data->at(loc.x).size()) < (loc.y + 1)) { + bel_data->at(loc.x).resize(loc.y + 1); + } + + bel_data->at(loc.x).at(loc.y).push_back(bel); + } + } + + void addBelBucket(BelBucketId partition) + { + auto iter = partition_types.find(partition); + if (iter != partition_types.end()) { + // This partition has already been added to the fast BEL lookup. + return; + } + + size_t type_idx = partition_types.size(); + auto &type_data = partition_types[partition]; + type_data.type_index = type_idx; + + fast_bels_by_partition_type.resize(type_idx + 1); + auto &bel_data = fast_bels_by_partition_type.at(type_idx); + NPNR_ASSERT(bel_data.get() == nullptr); + bel_data = std::make_unique(); + + for (auto bel : ctx->getBels()) { + if (ctx->getBelBucketForBel(bel) != partition) { + continue; + } + + type_data.number_of_possible_bels += 1; + } + + for (auto bel : ctx->getBels()) { + if (check_bel_available && !ctx->checkBelAvail(bel)) { + continue; + } + + if (ctx->getBelBucketForBel(bel) != partition) { + continue; + } + + Loc loc = ctx->getBelLocation(bel); + if (minBelsForGridPick >= 0 && type_data.number_of_possible_bels < minBelsForGridPick) { + loc.x = loc.y = 0; + } + + if (int(bel_data->size()) < (loc.x + 1)) { + bel_data->resize(loc.x + 1); + } + + if (int(bel_data->at(loc.x).size()) < (loc.y + 1)) { + bel_data->at(loc.x).resize(loc.y + 1); + } + + bel_data->at(loc.x).at(loc.y).push_back(bel); + } + } + + typedef std::vector>> FastBelsData; + + int getBelsForCellType(IdString cell_type, FastBelsData **data) + { + auto iter = cell_types.find(cell_type); + if (iter == cell_types.end()) { + addCellType(cell_type); + iter = cell_types.find(cell_type); + NPNR_ASSERT(iter != cell_types.end()); + } + + auto cell_type_data = iter->second; + + *data = fast_bels_by_cell_type.at(cell_type_data.type_index).get(); + return cell_type_data.number_of_possible_bels; + } + + size_t getBelsForBelBucket(BelBucketId partition, FastBelsData **data) + { + auto iter = partition_types.find(partition); + if (iter == partition_types.end()) { + addBelBucket(partition); + iter = partition_types.find(partition); + NPNR_ASSERT(iter != partition_types.end()); + } + + auto type_data = iter->second; + + *data = fast_bels_by_partition_type.at(type_data.type_index).get(); + return type_data.number_of_possible_bels; + } + + Context *ctx; + const bool check_bel_available; + const int minBelsForGridPick; + + dict cell_types; + std::vector> fast_bels_by_cell_type; + + dict partition_types; + std::vector> fast_bels_by_partition_type; +}; + +NEXTPNR_NAMESPACE_END diff --git a/common/place/parallel_refine.cc b/common/place/parallel_refine.cc new file mode 100644 index 00000000..a868ca58 --- /dev/null +++ b/common/place/parallel_refine.cc @@ -0,0 +1,959 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021-22 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 "parallel_refine.h" +#include "log.h" + +#if !defined(__wasm) + +#include "fast_bels.h" +#include "scope_lock.h" +#include "timing.h" + +#include +#include +#include +#include +#include + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +struct Partition +{ + int x0, y0, x1, y1; + std::vector cells; + Partition() = default; + explicit Partition(Context *ctx) + { + x0 = ctx->getGridDimX(); + y0 = ctx->getGridDimY(); + x1 = 0; + y1 = 0; + for (auto &cell : ctx->cells) { + Loc l = ctx->getBelLocation(cell.second->bel); + x0 = std::min(x0, l.x); + x1 = std::max(x1, l.x); + y0 = std::min(y0, l.y); + y1 = std::max(y1, l.y); + cells.push_back(cell.second.get()); + } + } + void split(Context *ctx, bool yaxis, float pivot, Partition &l, Partition &r) + { + std::sort(cells.begin(), cells.end(), [&](CellInfo *a, CellInfo *b) { + Loc l0 = ctx->getBelLocation(a->bel), l1 = ctx->getBelLocation(b->bel); + return yaxis ? (l0.y < l1.y) : (l0.x < l1.x); + }); + size_t pivot_point = size_t(cells.size() * pivot); + l.cells.clear(); + r.cells.clear(); + l.cells.reserve(pivot_point); + r.cells.reserve(cells.size() - pivot_point); + int pivot_coord = (pivot_point == 0) ? (yaxis ? y1 : x1) + : (yaxis ? ctx->getBelLocation(cells.at(pivot_point - 1)->bel).y + : ctx->getBelLocation(cells.at(pivot_point - 1)->bel).x); + for (size_t i = 0; i < cells.size(); i++) { + Loc loc = ctx->getBelLocation(cells.at(i)->bel); + ((yaxis ? loc.y : loc.x) <= pivot_coord ? l.cells : r.cells).push_back(cells.at(i)); + } + if (yaxis) { + l.x0 = r.x0 = x0; + l.x1 = r.x1 = x1; + l.y0 = y0; + l.y1 = pivot_coord; + r.y0 = (pivot_coord == y1) ? y1 : (pivot_coord + 1); + r.y1 = y1; + } else { + l.y0 = r.y0 = y0; + l.y1 = r.y1 = y1; + l.x0 = x0; + l.x1 = pivot_coord; + r.x0 = (pivot_coord == x1) ? x1 : (pivot_coord + 1); + r.x1 = x1; + } + } +}; + +typedef int64_t wirelen_t; + +struct NetBB +{ + // Actual bounding box + int x0 = 0, x1 = 0, y0 = 0, y1 = 0; + // Number of cells at each extremity + int nx0 = 0, nx1 = 0, ny0 = 0, ny1 = 0; + wirelen_t hpwl(const ParallelRefineCfg &cfg) const + { + return wirelen_t(cfg.hpwl_scale_x * (x1 - x0) + cfg.hpwl_scale_y * (y1 - y0)); + } + static NetBB compute(const Context *ctx, const NetInfo *net, const dict *cell2bel = nullptr) + { + NetBB result{}; + if (!net->driver.cell) + return result; + auto bel_loc = [&](const CellInfo *cell) { + BelId bel = cell2bel ? cell2bel->at(cell->name) : cell->bel; + return ctx->getBelLocation(bel); + }; + result.nx0 = result.nx1 = result.ny0 = result.ny1 = 1; + Loc drv_loc = bel_loc(net->driver.cell); + result.x0 = result.x1 = drv_loc.x; + result.y0 = result.y1 = drv_loc.y; + for (auto &usr : net->users) { + Loc l = bel_loc(usr.cell); + if (l.x == result.x0) + ++result.nx0; // on the edge + else if (l.x < result.x0) { + result.x0 = l.x; // extends the edge + result.nx0 = 1; + } + if (l.x == result.x1) + ++result.nx1; // on the edge + else if (l.x > result.x1) { + result.x1 = l.x; // extends the edge + result.nx1 = 1; + } + if (l.y == result.y0) + ++result.ny0; // on the edge + else if (l.y < result.y0) { + result.y0 = l.y; // extends the edge + result.ny0 = 1; + } + if (l.y == result.y1) + ++result.ny1; // on the edge + else if (l.y > result.y1) { + result.y1 = l.y; // extends the edge + result.ny1 = 1; + } + } + return result; + } +}; + +struct GlobalState +{ + explicit GlobalState(Context *ctx, ParallelRefineCfg cfg) : ctx(ctx), cfg(cfg), bels(ctx, false, 64), tmg(ctx){}; + Context *ctx; + ParallelRefineCfg cfg; + FastBels bels; + std::vector flat_nets; // flat array of all nets in the design for fast referencing by index + std::vector last_bounds; + std::vector> last_tmg_costs; + dict region_bounds; + TimingAnalyser tmg; + + std::shared_timed_mutex archapi_mutex; + + double temperature = 1e-7; + int radius = 3; + + wirelen_t total_wirelen = 0; + double total_timing_cost = 0; + + double get_timing_cost(const NetInfo *net, store_index user, + const dict *cell2bel = nullptr) + { + if (!net->driver.cell) + return 0; + const auto &sink = net->users.at(user); + IdString driver_pin, sink_pin; + // Pick the first pin for a prediction; assume all will be similar enouhg + for (auto pin : ctx->getBelPinsForCellPin(net->driver.cell, net->driver.port)) { + driver_pin = pin; + break; + } + for (auto pin : ctx->getBelPinsForCellPin(sink.cell, sink.port)) { + sink_pin = pin; + break; + } + float crit = tmg.get_criticality(CellPortKey(sink)); + BelId src_bel = cell2bel ? cell2bel->at(net->driver.cell->name) : net->driver.cell->bel; + BelId dst_bel = cell2bel ? cell2bel->at(sink.cell->name) : sink.cell->bel; + double delay = ctx->getDelayNS(ctx->predictDelay(src_bel, driver_pin, dst_bel, sink_pin)); + return delay * std::pow(crit, cfg.crit_exp); + } + + bool skip_net(const NetInfo *net) const + { + if (!net->driver.cell) + return true; + if (ctx->getBelGlobalBuf(net->driver.cell->bel)) + return true; + return false; + } + bool timing_skip_net(const NetInfo *net) const + { + if (!net->driver.cell) + return true; + int cc; + auto cls = ctx->getPortTimingClass(net->driver.cell, net->driver.port, cc); + if (cls == TMG_IGNORE || cls == TMG_GEN_CLOCK) + return true; + return false; + } + // .... +}; + +struct ThreadState +{ + Context *ctx; // Nextpnr context pointer + GlobalState &g; // Refinement engine state + int idx; // Index of the thread + DeterministicRNG rng; // Local RNG + // The cell partition that the thread works on + Partition p; + // Mapping from design-wide net index to thread-wide net index -- not all nets are in all partitions, so we can + // optimise + std::vector thread_net_idx; + // List of nets inside the partition; and their committed bounding boxes & timing costs from the thread's + // perspective + std::vector thread_nets; + std::vector net_bounds; + std::vector> arc_tmg_cost; + std::vector ignored_nets, tmg_ignored_nets; + bool arch_state_dirty = false; + // Our local cell-bel map; that won't be affected by out-of-partition moves + dict local_cell2bel; + + // Data on an inflight move + dict> moved_cells; // cell -> (old; new) + // For cluster moves only + std::vector> cell_rel; + // For incremental wirelength and delay updates + wirelen_t wirelen_delta = 0; + double timing_delta = 0; + + // Total made and accepted moved + int n_move = 0, n_accept = 0; + + enum BoundChange + { + NO_CHANGE, + CELL_MOVED_INWARDS, + CELL_MOVED_OUTWARDS, + FULL_RECOMPUTE + }; + // Wirelen related are handled on a per-axis basis to reduce + struct AxisChanges + { + std::vector bounds_changed_nets; + std::vector already_bounds_changed; + }; + std::array axes; + std::vector new_net_bounds; + + std::vector> already_timing_changed; + std::vector>> timing_changed_arcs; + std::vector new_timing_costs; + + ThreadState(Context *ctx, GlobalState &g, int idx) : ctx(ctx), g(g), idx(idx){}; + void set_partition(const Partition &part) + { + p = part; + thread_nets.clear(); + thread_net_idx.resize(g.flat_nets.size()); + std::fill(thread_net_idx.begin(), thread_net_idx.end(), -1); + // Determine the set of nets that are within the thread; and therefore we care about + for (auto thread_cell : part.cells) { + for (auto &port : thread_cell->ports) { + if (!port.second.net) + continue; + int global_idx = port.second.net->udata; + auto &thread_idx = thread_net_idx.at(global_idx); + // Already added to the set + if (thread_idx != -1) + continue; + thread_idx = thread_nets.size(); + thread_nets.push_back(port.second.net); + } + } + tmg_ignored_nets.clear(); + ignored_nets.clear(); + for (auto tn : thread_nets) { + ignored_nets.push_back(g.skip_net(tn)); + tmg_ignored_nets.push_back(g.timing_skip_net(tn)); + } + // Set up the original cell-bel map for all nets inside the thread + local_cell2bel.clear(); + for (NetInfo *net : thread_nets) { + if (net->driver.cell) + local_cell2bel[net->driver.cell->name] = net->driver.cell->bel; + for (auto &usr : net->users) + local_cell2bel[usr.cell->name] = usr.cell->bel; + } + } + void setup_initial_state() + { + // Setup initial net bounding boxes and timing costs + net_bounds.clear(); + arc_tmg_cost.clear(); + for (auto tn : thread_nets) { + net_bounds.push_back(g.last_bounds.at(tn->udata)); + arc_tmg_cost.push_back(g.last_tmg_costs.at(tn->udata)); + } + new_net_bounds = net_bounds; + for (int j = 0; j < 2; j++) { + auto &a = axes.at(j); + a.already_bounds_changed.resize(net_bounds.size()); + } + already_timing_changed.clear(); + already_timing_changed.resize(net_bounds.size()); + for (size_t i = 0; i < thread_nets.size(); i++) + already_timing_changed.at(i) = std::vector(thread_nets.at(i)->users.capacity()); + } + bool bounds_check(BelId bel) + { + Loc l = ctx->getBelLocation(bel); + if (l.x < p.x0 || l.x > p.x1 || l.y < p.y0 || l.y > p.y1) + return false; + return true; + } + bool bind_move() + { + std::unique_lock l(g.archapi_mutex); + for (auto &entry : moved_cells) { + ctx->unbindBel(entry.second.first); + } + bool success = true; + for (auto &entry : moved_cells) { + // Make sure targets are available before we bind them + if (!ctx->checkBelAvail(entry.second.second)) { + success = false; + break; + } + ctx->bindBel(entry.second.second, ctx->cells.at(entry.first).get(), STRENGTH_WEAK); + } + arch_state_dirty = true; + return success; + } + bool check_validity() + { + std::shared_lock l(g.archapi_mutex); + bool result = true; + for (auto e : moved_cells) { + if (!ctx->isBelLocationValid(e.second.first)) { + // Have to check old; too; as unbinding a bel could make a placement illegal by virtue of no longer + // enabling dedicated routes to be used + result = false; + break; + } + if (!ctx->isBelLocationValid(e.second.second)) { + result = false; + break; + } + } + return result; + } + void revert_move() + { + if (arch_state_dirty) { + // If changes to the arch state were made, revert them by restoring original cell bindings + std::unique_lock l(g.archapi_mutex); + for (auto &entry : moved_cells) { + BelId curr_bound = ctx->cells.at(entry.first)->bel; + if (curr_bound != BelId()) + ctx->unbindBel(curr_bound); + } + for (auto &entry : moved_cells) { + ctx->bindBel(entry.second.first, ctx->cells.at(entry.first).get(), STRENGTH_WEAK); + } + arch_state_dirty = false; + } + for (auto &entry : moved_cells) + local_cell2bel[entry.first] = entry.second.first; + } + void commit_move() + { + arch_state_dirty = false; + for (auto &axis : axes) { + for (auto bc : axis.bounds_changed_nets) { + // Commit updated net bounds + net_bounds.at(bc) = new_net_bounds.at(bc); + } + } + if (g.cfg.timing_driven) { + NPNR_ASSERT(timing_changed_arcs.size() == new_timing_costs.size()); + for (size_t i = 0; i < timing_changed_arcs.size(); i++) { + auto arc = timing_changed_arcs.at(i); + arc_tmg_cost.at(arc.first).at(arc.second.idx()) = new_timing_costs.at(i); + } + } + } + void compute_changes_for_cell(CellInfo *cell, BelId old_bel, BelId new_bel) + { + Loc new_loc = ctx->getBelLocation(new_bel); + Loc old_loc = ctx->getBelLocation(old_bel); + for (const auto &port : cell->ports) { + NetInfo *pn = port.second.net; + if (!pn) + continue; + int idx = thread_net_idx.at(pn->udata); + if (ignored_nets.at(idx)) + continue; + NetBB &new_bounds = new_net_bounds.at(idx); + // For the x-axis (i=0) and y-axis (i=1) + for (int i = 0; i < 2; i++) { + auto &axis = axes.at(i); + // New and old on this axis + int new_pos = i ? new_loc.y : new_loc.x, old_pos = i ? old_loc.y : old_loc.x; + // References to updated bounding box entries + auto &b0 = i ? new_bounds.y0 : new_bounds.x0; + auto &n0 = i ? new_bounds.ny0 : new_bounds.nx0; + auto &b1 = i ? new_bounds.y1 : new_bounds.x1; + auto &n1 = i ? new_bounds.ny1 : new_bounds.nx1; + auto &change = axis.already_bounds_changed.at(idx); + // Lower bound + if (new_pos < b0) { + // Further out than current lower bound + b0 = new_pos; + n0 = 1; + if (change == NO_CHANGE) { + change = CELL_MOVED_OUTWARDS; + axis.bounds_changed_nets.push_back(idx); + } + } else if (new_pos == b0 && old_pos > b0) { + // Moved from inside into current bound + ++n0; + if (change == NO_CHANGE) { + change = CELL_MOVED_OUTWARDS; + axis.bounds_changed_nets.push_back(idx); + } + } else if (old_pos == b0 && new_pos > b0) { + // Moved from current bound to inside + if (change == NO_CHANGE) + axis.bounds_changed_nets.push_back(idx); + if (n0 == 1) { + // Was the last cell on the bound; have to do a full recompute + change = FULL_RECOMPUTE; + } else { + --n0; + if (change == NO_CHANGE) + change = CELL_MOVED_INWARDS; + } + } + // Upper bound + if (new_pos > b1) { + // Further out than current upper bound + b1 = new_pos; + n1 = new_pos; + if (change == NO_CHANGE) { + change = CELL_MOVED_OUTWARDS; + axis.bounds_changed_nets.push_back(idx); + } + } else if (new_pos == b1 && old_pos < b1) { + // Moved onto current bound + ++n1; + if (change == NO_CHANGE) { + change = CELL_MOVED_OUTWARDS; + axis.bounds_changed_nets.push_back(idx); + } + } else if (old_pos == b1 && new_pos < b1) { + // Moved from current bound to inside + if (change == NO_CHANGE) + axis.bounds_changed_nets.push_back(idx); + if (n1 == 1) { + // Was the last cell on the bound; have to do a full recompute + change = FULL_RECOMPUTE; + } else { + --n1; + if (change == NO_CHANGE) + change = CELL_MOVED_INWARDS; + } + } + } + // Timing updates if timing driven + if (g.cfg.timing_driven && !tmg_ignored_nets.at(idx)) { + if (port.second.type == PORT_OUT) { + int cc; + TimingPortClass cls = ctx->getPortTimingClass(cell, port.first, cc); + if (cls != TMG_IGNORE) { + for (auto usr : pn->users.enumerate()) + if (!already_timing_changed.at(idx).at(usr.index.idx())) { + timing_changed_arcs.emplace_back(std::make_pair(idx, usr.index)); + already_timing_changed.at(idx).at(usr.index.idx()) = true; + } + } + } else { + auto usr = port.second.user_idx; + if (!already_timing_changed.at(idx).at(usr.idx())) { + timing_changed_arcs.emplace_back(std::make_pair(idx, usr)); + already_timing_changed.at(idx).at(usr.idx()) = true; + } + } + } + } + } + void compute_total_change() + { + auto &xa = axes.at(0), &ya = axes.at(1); + for (auto &bc : xa.bounds_changed_nets) + if (xa.already_bounds_changed.at(bc) == FULL_RECOMPUTE) + new_net_bounds.at(bc) = NetBB::compute(ctx, thread_nets.at(bc), &local_cell2bel); + for (auto &bc : ya.bounds_changed_nets) + if (xa.already_bounds_changed.at(bc) != FULL_RECOMPUTE && + ya.already_bounds_changed.at(bc) == FULL_RECOMPUTE) + new_net_bounds.at(bc) = NetBB::compute(ctx, thread_nets.at(bc), &local_cell2bel); + for (auto &bc : xa.bounds_changed_nets) + wirelen_delta += (new_net_bounds.at(bc).hpwl(g.cfg) - net_bounds.at(bc).hpwl(g.cfg)); + for (auto &bc : ya.bounds_changed_nets) + if (xa.already_bounds_changed.at(bc) == NO_CHANGE) + wirelen_delta += (new_net_bounds.at(bc).hpwl(g.cfg) - net_bounds.at(bc).hpwl(g.cfg)); + if (g.cfg.timing_driven) { + NPNR_ASSERT(new_timing_costs.empty()); + for (auto arc : timing_changed_arcs) { + double new_cost = g.get_timing_cost(thread_nets.at(arc.first), arc.second, &local_cell2bel); + timing_delta += (new_cost - arc_tmg_cost.at(arc.first).at(arc.second.idx())); + new_timing_costs.push_back(new_cost); + } + } + } + void reset_move_state() + { + moved_cells.clear(); + cell_rel.clear(); + for (auto &axis : axes) { + for (auto bc : axis.bounds_changed_nets) { + new_net_bounds.at(bc) = net_bounds.at(bc); + axis.already_bounds_changed[bc] = NO_CHANGE; + } + axis.bounds_changed_nets.clear(); + } + for (auto &arc : timing_changed_arcs) { + already_timing_changed.at(arc.first).at(arc.second.idx()) = false; + } + timing_changed_arcs.clear(); + new_timing_costs.clear(); + wirelen_delta = 0; + timing_delta = 0; + } + + bool accept_move() + { + static constexpr double epsilon = 1e-20; + double delta = g.cfg.lambda * (timing_delta / std::max(epsilon, g.total_timing_cost)) + + (1.0 - g.cfg.lambda) * (double(wirelen_delta) / std::max(epsilon, g.total_wirelen)); + return delta < 0 || + (g.temperature > 1e-8 && (rng.rng() / float(0x3fffffff)) <= std::exp(-delta / g.temperature)); + } + + bool add_to_move(CellInfo *cell, BelId old_bel, BelId new_bel) + { + if (!bounds_check(old_bel) || !bounds_check(new_bel)) + return false; + if (!ctx->isValidBelForCellType(cell->type, new_bel)) + return false; + NPNR_ASSERT(!moved_cells.count(cell->name)); + moved_cells[cell->name] = std::make_pair(old_bel, new_bel); + local_cell2bel[cell->name] = new_bel; + compute_changes_for_cell(cell, old_bel, new_bel); + return true; + } + + bool single_cell_swap(CellInfo *cell, BelId new_bel) + { + NPNR_ASSERT(moved_cells.empty()); + BelId old_bel = cell->bel; + CellInfo *bound = ctx->getBoundBelCell(new_bel); + if (bound && (bound->belStrength > STRENGTH_STRONG || bound->cluster != ClusterId())) + return false; + if (!add_to_move(cell, old_bel, new_bel)) + goto fail; + if (bound && !add_to_move(bound, new_bel, old_bel)) + goto fail; + compute_total_change(); + // SA acceptance criteria + + if (!accept_move()) { + // SA fail + goto fail; + } + // Check validity rules + if (!bind_move()) + goto fail; + if (!check_validity()) + goto fail; + // Accepted! + commit_move(); + reset_move_state(); + return true; + fail: + revert_move(); + reset_move_state(); + return false; + } + + bool chain_swap(CellInfo *root_cell, BelId new_root_bel) + { + NPNR_ASSERT(moved_cells.empty()); + std::queue> displaced_clusters; + pool used_bels; + displaced_clusters.emplace(root_cell->cluster, new_root_bel); + while (!displaced_clusters.empty()) { + std::vector> dest_bels; + auto cursor = displaced_clusters.front(); + displaced_clusters.pop(); + if (!ctx->getClusterPlacement(cursor.first, cursor.second, dest_bels)) + goto fail; + for (const auto &db : dest_bels) { + BelId old_bel = db.first->bel; + if (moved_cells.count(db.first->name)) + goto fail; + if (!add_to_move(db.first, old_bel, db.second)) + goto fail; + if (used_bels.count(db.second)) + goto fail; + used_bels.insert(db.second); + + CellInfo *bound = ctx->getBoundBelCell(db.second); + if (bound) { + if (moved_cells.count(bound->name)) { + // Don't move a cell multiple times in the same go + goto fail; + } else if (bound->belStrength > STRENGTH_STRONG) { + goto fail; + } else if (bound->cluster != ClusterId()) { + // Displace the entire cluster + Loc old_loc = ctx->getBelLocation(old_bel); + Loc bound_loc = ctx->getBelLocation(bound->bel); + Loc root_loc = ctx->getBelLocation(ctx->getClusterRootCell(bound->cluster)->bel); + BelId new_root = ctx->getBelByLocation(Loc(old_loc.x + (root_loc.x - bound_loc.x), + old_loc.y + (root_loc.y - bound_loc.y), + old_loc.z + (root_loc.z - bound_loc.z))); + if (new_root == BelId()) + goto fail; + displaced_clusters.emplace(bound->cluster, new_root); + } else { + // Single cell swap + if (used_bels.count(old_bel)) + goto fail; + used_bels.insert(old_bel); + if (!add_to_move(bound, bound->bel, old_bel)) + goto fail; + } + } else if (!ctx->checkBelAvail(db.second)) { + goto fail; + } + } + } + compute_total_change(); + // SA acceptance criteria + + if (!accept_move()) { + // SA fail + goto fail; + } + // Check validity rules + if (!bind_move()) + goto fail; + if (!check_validity()) + goto fail; + // Accepted! + commit_move(); + reset_move_state(); + return true; + fail: + revert_move(); + reset_move_state(); + return false; + } + + BelId random_bel_for_cell(CellInfo *cell, int force_z = -1) + { + IdString targetType = cell->type; + Loc curr_loc = ctx->getBelLocation(cell->bel); + int count = 0; + + int dx = g.radius, dy = g.radius; + if (cell->region != nullptr && cell->region->constr_bels) { + dx = std::min(g.cfg.hpwl_scale_x * g.radius, + (g.region_bounds[cell->region->name].x1 - g.region_bounds[cell->region->name].x0) + 1); + dy = std::min(g.cfg.hpwl_scale_y * g.radius, + (g.region_bounds[cell->region->name].y1 - g.region_bounds[cell->region->name].y0) + 1); + // Clamp location to within bounds + curr_loc.x = std::max(g.region_bounds[cell->region->name].x0, curr_loc.x); + curr_loc.x = std::min(g.region_bounds[cell->region->name].x1, curr_loc.x); + curr_loc.y = std::max(g.region_bounds[cell->region->name].y0, curr_loc.y); + curr_loc.y = std::min(g.region_bounds[cell->region->name].y1, curr_loc.y); + } + + FastBels::FastBelsData *bel_data; + auto type_cnt = g.bels.getBelsForCellType(targetType, &bel_data); + + while (true) { + int nx = rng.rng(2 * dx + 1) + std::max(curr_loc.x - dx, 0); + int ny = rng.rng(2 * dy + 1) + std::max(curr_loc.y - dy, 0); + if (type_cnt < 64) + nx = ny = 0; + if (nx >= int(bel_data->size())) + continue; + if (ny >= int(bel_data->at(nx).size())) + continue; + const auto &fb = bel_data->at(nx).at(ny); + if (fb.size() == 0) + continue; + BelId bel = fb.at(rng.rng(int(fb.size()))); + if (!bounds_check(bel)) + continue; + if (force_z != -1) { + Loc loc = ctx->getBelLocation(bel); + if (loc.z != force_z) + continue; + } + if (!cell->testRegion(bel)) + continue; + count++; + return bel; + } + } + + void run_iter() + { + setup_initial_state(); + n_accept = 0; + n_move = 0; + for (int m = 0; m < g.cfg.inner_iters; m++) { + for (auto cell : p.cells) { + if (cell->belStrength > STRENGTH_STRONG) + continue; + if (cell->cluster != ClusterId()) { + if (cell != ctx->getClusterRootCell(cell->cluster)) + continue; // only move cluster root + Loc old_loc = ctx->getBelLocation(cell->bel); + BelId new_root = random_bel_for_cell(cell, old_loc.z); + if (new_root == BelId() || new_root == cell->bel) + continue; + ++n_move; + if (chain_swap(cell, new_root)) + ++n_accept; + } else { + BelId new_bel = random_bel_for_cell(cell); + if (new_bel == BelId() || new_bel == cell->bel) + continue; + ++n_move; + if (single_cell_swap(cell, new_bel)) + ++n_accept; + } + } + } + } +}; + +struct ParallelRefine +{ + Context *ctx; + GlobalState g; + std::vector t; + ParallelRefine(Context *ctx, ParallelRefineCfg cfg) : ctx(ctx), g(ctx, cfg) + { + g.flat_nets.reserve(ctx->nets.size()); + for (auto &net : ctx->nets) { + net.second->udata = g.flat_nets.size(); + g.flat_nets.push_back(net.second.get()); + } + // Setup per thread context + for (int i = 0; i < cfg.threads; i++) { + t.emplace_back(ctx, g, i); + } + // Setup region bounds + for (auto ®ion : ctx->region) { + Region *r = region.second.get(); + NetBB bb; + if (r->constr_bels) { + bb.x0 = std::numeric_limits::max(); + bb.x1 = std::numeric_limits::min(); + bb.y0 = std::numeric_limits::max(); + bb.y1 = std::numeric_limits::min(); + for (auto bel : r->bels) { + Loc loc = ctx->getBelLocation(bel); + bb.x0 = std::min(bb.x0, loc.x); + bb.x1 = std::max(bb.x1, loc.x); + bb.y0 = std::min(bb.y0, loc.y); + bb.y1 = std::max(bb.y1, loc.y); + } + } else { + bb.x0 = 0; + bb.y0 = 0; + bb.x1 = ctx->getGridDimX(); + bb.y1 = ctx->getGridDimY(); + } + g.region_bounds[r->name] = bb; + } + // Setup fast bels map + pool cell_types_in_use; + for (auto &cell : ctx->cells) { + IdString cell_type = cell.second->type; + cell_types_in_use.insert(cell_type); + } + + for (auto cell_type : cell_types_in_use) { + g.bels.addCellType(cell_type); + } + }; + std::vector parts; + void do_partition() + { + parts.clear(); + parts.emplace_back(ctx); + bool yaxis = false; + while (parts.size() < t.size()) { + std::vector next(parts.size() * 2); + for (size_t i = 0; i < parts.size(); i++) { + // Randomly permute pivot every iteration so we get different thread boundaries + const float delta = 0.1; + float pivot = (0.5 - (delta / 2)) + (delta / 2) * (ctx->rng(10000) / 10000.0f); + parts.at(i).split(ctx, yaxis, pivot, next.at(i * 2), next.at(i * 2 + 1)); + } + std::swap(parts, next); + yaxis = !yaxis; + } + + NPNR_ASSERT(parts.size() == t.size()); + // TODO: thread pool to make this worthwhile... + std::vector workers; + for (size_t i = 0; i < t.size(); i++) { + workers.emplace_back([i, this]() { t.at(i).set_partition(parts.at(i)); }); + } + for (auto &w : workers) + w.join(); + } + + void update_global_costs() + { + g.last_bounds.resize(g.flat_nets.size()); + g.last_tmg_costs.resize(g.flat_nets.size()); + g.total_wirelen = 0; + g.total_timing_cost = 0; + for (size_t i = 0; i < g.flat_nets.size(); i++) { + NetInfo *ni = g.flat_nets.at(i); + if (g.skip_net(ni)) + continue; + g.last_bounds.at(i) = NetBB::compute(ctx, ni); + g.total_wirelen += g.last_bounds.at(i).hpwl(g.cfg); + if (!g.timing_skip_net(ni)) { + auto &tc = g.last_tmg_costs.at(i); + tc.resize(ni->users.capacity()); + for (auto usr : ni->users.enumerate()) { + tc.at(usr.index.idx()) = g.get_timing_cost(ni, usr.index); + g.total_timing_cost += tc.at(usr.index.idx()); + } + } + } + } + void run() + { + + ScopeLock lock(ctx); + auto refine_start = std::chrono::high_resolution_clock::now(); + + g.tmg.setup_only = true; + g.tmg.setup(); + do_partition(); + log_info("Running parallel refinement with %d threads.\n", int(t.size())); + int iter = 1; + bool done = false; + update_global_costs(); + double avg_wirelen = g.total_wirelen; + wirelen_t min_wirelen = g.total_wirelen; + while (true) { + if (iter > 1) { + if (g.total_wirelen >= min_wirelen) { + done = true; + } else if (g.total_wirelen < min_wirelen) { + min_wirelen = g.total_wirelen; + } + int n_accept = 0, n_move = 0; + for (auto &t_data : t) { + n_accept += t_data.n_accept; + n_move += t_data.n_move; + } + double r_accept = n_accept / double(n_move); + if (g.total_wirelen < (0.95 * avg_wirelen) && g.total_wirelen > 0) { + avg_wirelen = 0.8 * avg_wirelen + 0.2 * g.total_wirelen; + } else { + if (r_accept > 0.15 && g.radius > 1) { + g.temperature *= 0.95; + } else { + g.temperature *= 0.8; + } + } + if ((iter % 10) == 0 && g.radius > 1) + --g.radius; + } + + if ((iter == 1) || ((iter % 5) == 0) || done) + log_info(" at iteration #%d: temp = %f, timing cost = " + "%.0f, wirelen = %.0f\n", + iter, g.temperature, double(g.total_timing_cost), double(g.total_wirelen)); + + if (done) + break; + + do_partition(); + + std::vector workers; + workers.reserve(t.size()); + for (int j = 0; j < int(t.size()); j++) + workers.emplace_back([this, j]() { t.at(j).run_iter(); }); + for (auto &w : workers) + w.join(); + g.tmg.run(); + update_global_costs(); + iter++; + ctx->yield(); + } + auto refine_end = std::chrono::high_resolution_clock::now(); + log_info("Placement refine time %.02fs\n", std::chrono::duration(refine_end - refine_start).count()); + } +}; +} // namespace + +ParallelRefineCfg::ParallelRefineCfg(Context *ctx) +{ + timing_driven = ctx->setting("timing_driven"); + threads = ctx->setting("threads", 8); + // snap to nearest power of two; and minimum thread size + int actual_threads = 1; + while ((actual_threads * 2) <= threads && (int(ctx->cells.size()) / (actual_threads * 2)) >= min_thread_size) + actual_threads *= 2; + threads = actual_threads; + hpwl_scale_x = 1; + hpwl_scale_y = 1; +} + +bool parallel_refine(Context *ctx, ParallelRefineCfg cfg) +{ + // TODO + ParallelRefine refine(ctx, cfg); + refine.run(); + timing_analysis(ctx); + return true; +} + +NEXTPNR_NAMESPACE_END + +#else /* !defined(__wasm) */ + +NEXTPNR_NAMESPACE_BEGIN + +bool parallel_refine(Context *ctx, ParallelRefineCfg cfg) { log_abort(); } + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/place/parallel_refine.h b/common/place/parallel_refine.h new file mode 100644 index 00000000..556317cd --- /dev/null +++ b/common/place/parallel_refine.h @@ -0,0 +1,43 @@ +/* + * 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. + * + */ + +#ifndef PARALLEL_REFINE_H +#define PARALLEL_REFINE_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct ParallelRefineCfg +{ + ParallelRefineCfg(Context *ctx); + bool timing_driven; + int threads; + int hpwl_scale_x, hpwl_scale_y; + double lambda = 0.5f; + float crit_exp = 8; + int inner_iters = 15; + int min_thread_size = 500; +}; + +bool parallel_refine(Context *ctx, ParallelRefineCfg cfg); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/place/place_common.cc b/common/place/place_common.cc new file mode 100644 index 00000000..e03fca55 --- /dev/null +++ b/common/place/place_common.cc @@ -0,0 +1,487 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * 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. + * + */ + +#include "place_common.h" +#include +#include "log.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +// Get the total estimated wirelength for a net +wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type, float &tns) +{ + wirelen_t wirelength = 0; + CellInfo *driver_cell = net->driver.cell; + if (!driver_cell) + return 0; + if (driver_cell->bel == BelId()) + return 0; + bool driver_gb = ctx->getBelGlobalBuf(driver_cell->bel); + if (driver_gb) + return 0; + int clock_count; + bool timing_driven = ctx->setting("timing_driven") && type == MetricType::COST && + ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) != TMG_IGNORE; + delay_t negative_slack = 0; + delay_t worst_slack = std::numeric_limits::max(); + Loc driver_loc = ctx->getBelLocation(driver_cell->bel); + int xmin = driver_loc.x, xmax = driver_loc.x, ymin = driver_loc.y, ymax = driver_loc.y; + for (auto load : net->users) { + if (load.cell == nullptr) + continue; + CellInfo *load_cell = load.cell; + if (load_cell->bel == BelId()) + continue; + if (timing_driven) { + delay_t net_delay = ctx->predictArcDelay(net, load); + auto slack = load.budget - net_delay; + if (slack < 0) + negative_slack += slack; + worst_slack = std::min(slack, worst_slack); + } + + if (ctx->getBelGlobalBuf(load_cell->bel)) + continue; + Loc load_loc = ctx->getBelLocation(load_cell->bel); + + xmin = std::min(xmin, load_loc.x); + ymin = std::min(ymin, load_loc.y); + xmax = std::max(xmax, load_loc.x); + ymax = std::max(ymax, load_loc.y); + } + if (timing_driven) { + wirelength = wirelen_t( + (((ymax - ymin) + (xmax - xmin)) * std::min(5.0, (1.0 + std::exp(-ctx->getDelayNS(worst_slack) / 5))))); + } else { + wirelength = wirelen_t((ymax - ymin) + (xmax - xmin)); + } + + tns += ctx->getDelayNS(negative_slack); + return wirelength; +} + +// Get the total wirelength for a cell +wirelen_t get_cell_metric(const Context *ctx, const CellInfo *cell, MetricType type) +{ + std::set nets; + for (auto p : cell->ports) { + if (p.second.net) + nets.insert(p.second.net->name); + } + wirelen_t wirelength = 0; + float tns = 0; + for (auto n : nets) { + wirelength += get_net_metric(ctx, ctx->nets.at(n).get(), type, tns); + } + return wirelength; +} + +wirelen_t get_cell_metric_at_bel(const Context *ctx, CellInfo *cell, BelId bel, MetricType type) +{ + BelId oldBel = cell->bel; + cell->bel = bel; + wirelen_t wirelen = get_cell_metric(ctx, cell, type); + cell->bel = oldBel; + return wirelen; +} + +// Placing a single cell +bool place_single_cell(Context *ctx, CellInfo *cell, bool require_legality) +{ + bool all_placed = false; + int iters = 25; + while (!all_placed) { + BelId best_bel = BelId(); + wirelen_t best_wirelen = std::numeric_limits::max(), + best_ripup_wirelen = std::numeric_limits::max(); + CellInfo *ripup_target = nullptr; + BelId ripup_bel = BelId(); + if (cell->bel != BelId()) { + ctx->unbindBel(cell->bel); + } + IdString targetType = cell->type; + for (auto bel : ctx->getBels()) { + if (ctx->isValidBelForCellType(targetType, bel)) { + if (ctx->checkBelAvail(bel)) { + wirelen_t wirelen = get_cell_metric_at_bel(ctx, cell, bel, MetricType::COST); + if (iters >= 4) + wirelen += ctx->rng(25); + if (wirelen <= best_wirelen) { + best_wirelen = wirelen; + best_bel = bel; + } + } else { + wirelen_t wirelen = get_cell_metric_at_bel(ctx, cell, bel, MetricType::COST); + if (iters >= 4) + wirelen += ctx->rng(25); + if (wirelen <= best_ripup_wirelen) { + CellInfo *curr_cell = ctx->getBoundBelCell(bel); + if (curr_cell->belStrength < STRENGTH_STRONG) { + best_ripup_wirelen = wirelen; + ripup_bel = bel; + ripup_target = curr_cell; + } + } + } + } + } + if (best_bel == BelId()) { + if (iters == 0) { + log_error("failed to place cell '%s' of type '%s' (ripup iteration limit exceeded)\n", + cell->name.c_str(ctx), cell->type.c_str(ctx)); + } + if (ripup_bel == BelId()) { + log_error("failed to place cell '%s' of type '%s'\n", cell->name.c_str(ctx), cell->type.c_str(ctx)); + } + --iters; + ctx->unbindBel(ripup_target->bel); + best_bel = ripup_bel; + } else { + ripup_target = nullptr; + all_placed = true; + } + ctx->bindBel(best_bel, cell, STRENGTH_WEAK); + if (require_legality && !ctx->isBelLocationValid(best_bel)) { + ctx->unbindBel(best_bel); + if (ripup_target != nullptr) { + ctx->bindBel(best_bel, ripup_target, STRENGTH_WEAK); + } + all_placed = false; + continue; + } + if (ctx->verbose) + log_info(" placed single cell '%s' at '%s'\n", cell->name.c_str(ctx), ctx->nameOfBel(best_bel)); + cell = ripup_target; + } + return true; +} + +class ConstraintLegaliseWorker +{ + private: + Context *ctx; + std::set rippedCells; + dict oldLocations; + dict> cluster2cells; + + class IncreasingDiameterSearch + { + public: + IncreasingDiameterSearch() : start(0), min(0), max(-1){}; + IncreasingDiameterSearch(int x) : start(x), min(x), max(x){}; + IncreasingDiameterSearch(int start, int min, int max) : start(start), min(min), max(max){}; + bool done() const { return (diameter > (max - min)); }; + int get() const + { + int val = start + sign * diameter; + val = std::max(val, min); + val = std::min(val, max); + return val; + } + + void next() + { + if (sign == 0) { + sign = 1; + diameter = 1; + } else if (sign == -1) { + sign = 1; + if ((start + sign * diameter) > max) + sign = -1; + ++diameter; + } else { + sign = -1; + if ((start + sign * diameter) < min) { + sign = 1; + ++diameter; + } + } + } + + void reset() + { + sign = 0; + diameter = 0; + } + + private: + int start, min, max; + int diameter = 0; + int sign = 0; + }; + + typedef dict CellLocations; + + // Check if a location would be suitable for a cell and all its constrained children + bool valid_loc_for(const CellInfo *cell, Loc loc, CellLocations &solution, pool &usedLocations) + { + BelId locBel = ctx->getBelByLocation(loc); + if (locBel == BelId()) + return false; + + if (cell->cluster == ClusterId()) { + if (!ctx->isValidBelForCellType(cell->type, locBel)) + return false; + if (!ctx->checkBelAvail(locBel)) { + CellInfo *confCell = ctx->getConflictingBelCell(locBel); + if (confCell->belStrength >= STRENGTH_STRONG) { + return false; + } + } + // Don't place at tiles where any strongly bound Bels exist, as we might need to rip them up later + for (auto tilebel : ctx->getBelsByTile(loc.x, loc.y)) { + CellInfo *tcell = ctx->getBoundBelCell(tilebel); + if (tcell && tcell->belStrength >= STRENGTH_STRONG) + return false; + } + usedLocations.insert(loc); + solution[cell->name] = loc; + } else { + std::vector> placement; + if (!ctx->getClusterPlacement(cell->cluster, locBel, placement)) + return false; + for (auto &p : placement) { + Loc p_loc = ctx->getBelLocation(p.second); + if (!ctx->checkBelAvail(p.second)) { + CellInfo *confCell = ctx->getConflictingBelCell(p.second); + if (confCell->belStrength >= STRENGTH_STRONG) { + return false; + } + } + // Don't place at tiles where any strongly bound Bels exist, as we might need to rip them up later + for (auto tilebel : ctx->getBelsByTile(p_loc.x, p_loc.y)) { + CellInfo *tcell = ctx->getBoundBelCell(tilebel); + if (tcell && tcell->belStrength >= STRENGTH_STRONG) + return false; + } + usedLocations.insert(p_loc); + solution[p.first->name] = p_loc; + } + } + + return true; + } + + // Set the strength to locked on all cells in chain + void lockdown_chain(CellInfo *root) + { + root->belStrength = STRENGTH_STRONG; + if (root->cluster != ClusterId()) + for (auto child : cluster2cells.at(root->cluster)) + child->belStrength = STRENGTH_STRONG; + } + + // Legalise placement constraints on a cell + bool legalise_cell(CellInfo *cell) + { + if (cell->cluster != ClusterId() && ctx->getClusterRootCell(cell->cluster) != cell) + return true; // Only process chain roots + if (constraints_satisfied(cell)) { + if (cell->cluster != ClusterId()) + lockdown_chain(cell); + } else { + IncreasingDiameterSearch xRootSearch, yRootSearch, zRootSearch; + Loc currentLoc; + if (cell->bel != BelId()) + currentLoc = ctx->getBelLocation(cell->bel); + else + currentLoc = oldLocations[cell->name]; + xRootSearch = IncreasingDiameterSearch(currentLoc.x, 0, ctx->getGridDimX() - 1); + yRootSearch = IncreasingDiameterSearch(currentLoc.y, 0, ctx->getGridDimY() - 1); + zRootSearch = IncreasingDiameterSearch(currentLoc.z, 0, ctx->getTileBelDimZ(currentLoc.x, currentLoc.y)); + + while (!xRootSearch.done()) { + Loc rootLoc; + + rootLoc.x = xRootSearch.get(); + rootLoc.y = yRootSearch.get(); + rootLoc.z = zRootSearch.get(); + zRootSearch.next(); + if (zRootSearch.done()) { + zRootSearch.reset(); + yRootSearch.next(); + if (yRootSearch.done()) { + yRootSearch.reset(); + xRootSearch.next(); + } + } + + CellLocations solution; + pool used; + if (valid_loc_for(cell, rootLoc, solution, used)) { + for (auto cp : solution) { + // First unbind all cells + if (ctx->cells.at(cp.first)->bel != BelId()) + ctx->unbindBel(ctx->cells.at(cp.first)->bel); + } + for (auto cp : solution) { + if (ctx->verbose) + log_info(" placing '%s' at (%d, %d, %d)\n", cp.first.c_str(ctx), cp.second.x, + cp.second.y, cp.second.z); + BelId target = ctx->getBelByLocation(cp.second); + if (!ctx->checkBelAvail(target)) { + CellInfo *confl_cell = ctx->getConflictingBelCell(target); + if (confl_cell != nullptr) { + if (ctx->verbose) + log_info(" '%s' already placed at '%s'\n", ctx->nameOf(confl_cell), + ctx->nameOfBel(confl_cell->bel)); + NPNR_ASSERT(confl_cell->belStrength < STRENGTH_STRONG); + ctx->unbindBel(target); + rippedCells.insert(confl_cell->name); + } + } + ctx->bindBel(target, ctx->cells.at(cp.first).get(), STRENGTH_STRONG); + rippedCells.erase(cp.first); + } + for (auto cp : solution) { + for (auto bel : ctx->getBelsByTile(cp.second.x, cp.second.y)) { + CellInfo *belCell = ctx->getBoundBelCell(bel); + if (belCell != nullptr && !solution.count(belCell->name)) { + if (!ctx->isBelLocationValid(bel)) { + NPNR_ASSERT(belCell->belStrength < STRENGTH_STRONG); + ctx->unbindBel(bel); + rippedCells.insert(belCell->name); + } + } + } + } + NPNR_ASSERT(constraints_satisfied(cell)); + return true; + } + } + return false; + } + return true; + } + + // Check if constraints are currently satisfied on a cell and its children + bool constraints_satisfied(const CellInfo *cell) { return get_constraints_distance(ctx, cell) == 0; } + + public: + ConstraintLegaliseWorker(Context *ctx) : ctx(ctx) + { + for (auto &cell : ctx->cells) { + if (cell.second->cluster != ClusterId()) + cluster2cells[cell.second->cluster].push_back(cell.second.get()); + } + }; + + unsigned print_stats(const char *point) + { + float distance_sum = 0; + float max_distance = 0; + unsigned moved_cells = 0; + unsigned unplaced_cells = 0; + for (auto orig : oldLocations) { + if (ctx->cells.at(orig.first)->bel == BelId()) { + unplaced_cells++; + continue; + } + Loc newLoc = ctx->getBelLocation(ctx->cells.at(orig.first)->bel); + if (newLoc != orig.second) { + float distance = std::sqrt(std::pow(newLoc.x - orig.second.x, 2) + pow(newLoc.y - orig.second.y, 2)); + moved_cells++; + distance_sum += distance; + if (distance > max_distance) + max_distance = distance; + } + } + log_info(" moved %d cells, %d unplaced (after %s)\n", moved_cells, unplaced_cells, point); + if (moved_cells > 0) { + log_info(" average distance %f\n", (distance_sum / moved_cells)); + log_info(" maximum distance %f\n", max_distance); + } + return moved_cells + unplaced_cells; + } + + int legalise_constraints() + { + log_info("Legalising relative constraints...\n"); + for (auto &cell : ctx->cells) { + oldLocations[cell.first] = ctx->getBelLocation(cell.second->bel); + } + for (auto &cell : ctx->cells) { + bool res = legalise_cell(cell.second.get()); + if (!res) { + log_error("failed to place chain starting at cell '%s'\n", cell.first.c_str(ctx)); + return -1; + } + } + if (print_stats("legalising chains") == 0) + return 0; + for (auto rippedCell : rippedCells) { + bool res = place_single_cell(ctx, ctx->cells.at(rippedCell).get(), true); + if (!res) { + log_error("failed to place cell '%s' after relative constraint legalisation\n", rippedCell.c_str(ctx)); + return -1; + } + } + auto score = print_stats("replacing ripped up cells"); + for (auto &cell : ctx->cells) + if (get_constraints_distance(ctx, cell.second.get()) != 0) + log_error("constraint satisfaction check failed for cell '%s' at Bel '%s'\n", cell.first.c_str(ctx), + ctx->nameOfBel(cell.second->bel)); + return score; + } +}; + +bool legalise_relative_constraints(Context *ctx) { return ConstraintLegaliseWorker(ctx).legalise_constraints() > 0; } + +// Get the total distance from satisfied constraints for a cell +int get_constraints_distance(const Context *ctx, const CellInfo *cell) +{ + int dist = 0; + if (cell->bel == BelId()) + return 100000; + Loc loc = ctx->getBelLocation(cell->bel); + + if (cell->cluster != ClusterId()) { + CellInfo *root = ctx->getClusterRootCell(cell->cluster); + if (root == cell) { + // parent + std::vector> placement; + if (!ctx->getClusterPlacement(cell->cluster, cell->bel, placement)) { + return 100000; + } else { + for (const auto &p : placement) { + if (p.first->bel == BelId()) + return 100000; + Loc c_loc = ctx->getBelLocation(p.first->bel); + Loc p_loc = ctx->getBelLocation(p.second); + dist += std::abs(c_loc.x - p_loc.x); + dist += std::abs(c_loc.y - p_loc.y); + dist += std::abs(c_loc.z - p_loc.z); + } + } + } else { + // child + if (root->bel == BelId()) + return 100000; + Loc root_loc = ctx->getBelLocation(root->bel); + Loc offset = ctx->getClusterOffset(cell); + dist += std::abs((root_loc.x + offset.x) - loc.x); + dist += std::abs((root_loc.y + offset.y) - loc.y); + } + } + + return dist; +} + +NEXTPNR_NAMESPACE_END diff --git a/common/place/place_common.h b/common/place/place_common.h new file mode 100644 index 00000000..5e5cbee3 --- /dev/null +++ b/common/place/place_common.h @@ -0,0 +1,55 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * 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 PLACE_COMMON_H +#define PLACE_COMMON_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +typedef int64_t wirelen_t; + +enum class MetricType +{ + COST, + WIRELENGTH +}; + +// Return the wirelength of a net +wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type, float &tns); + +// Return the wirelength of all nets connected to a cell +wirelen_t get_cell_metric(const Context *ctx, const CellInfo *cell, MetricType type); + +// Return the wirelength of all nets connected to a cell, when the cell is at a given bel +wirelen_t get_cell_metric_at_bel(const Context *ctx, CellInfo *cell, BelId bel, MetricType type); + +// Place a single cell in the lowest wirelength Bel available, optionally requiring validity check +bool place_single_cell(Context *ctx, CellInfo *cell, bool require_legality); + +// Modify a design s.t. all relative placement constraints are satisfied +bool legalise_relative_constraints(Context *ctx); + +// Get the total distance from satisfied constraints for a cell +int get_constraints_distance(const Context *ctx, const CellInfo *cell); + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/place/placer1.cc b/common/place/placer1.cc new file mode 100644 index 00000000..a6ba3895 --- /dev/null +++ b/common/place/placer1.cc @@ -0,0 +1,1317 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * Copyright (C) 2018 gatecat + * + * Simulated annealing implementation based on arachne-pnr + * Copyright (C) 2015-2018 Cotton Seed + * + * 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 "placer1.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fast_bels.h" +#include "log.h" +#include "place_common.h" +#include "scope_lock.h" +#include "timing.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +class SAPlacer +{ + private: + struct BoundingBox + { + // Actual bounding box + int x0 = 0, x1 = 0, y0 = 0, y1 = 0; + // Number of cells at each extremity + int nx0 = 0, nx1 = 0, ny0 = 0, ny1 = 0; + wirelen_t hpwl(const Placer1Cfg &cfg) const + { + return wirelen_t(cfg.hpwl_scale_x * (x1 - x0) + cfg.hpwl_scale_y * (y1 - y0)); + } + }; + + public: + SAPlacer(Context *ctx, Placer1Cfg cfg) + : ctx(ctx), fast_bels(ctx, /*check_bel_available=*/false, cfg.minBelsForGridPick), cfg(cfg), tmg(ctx) + { + for (auto bel : ctx->getBels()) { + Loc loc = ctx->getBelLocation(bel); + max_x = std::max(max_x, loc.x); + max_y = std::max(max_y, loc.y); + } + diameter = std::max(max_x, max_y) + 1; + + pool cell_types_in_use; + for (auto &cell : ctx->cells) { + IdString cell_type = cell.second->type; + cell_types_in_use.insert(cell_type); + } + + for (auto cell_type : cell_types_in_use) { + fast_bels.addCellType(cell_type); + } + + net_bounds.resize(ctx->nets.size()); + net_arc_tcost.resize(ctx->nets.size()); + old_udata.reserve(ctx->nets.size()); + net_by_udata.reserve(ctx->nets.size()); + decltype(NetInfo::udata) n = 0; + for (auto &net : ctx->nets) { + old_udata.emplace_back(net.second->udata); + net_arc_tcost.at(n).resize(net.second->users.capacity()); + net.second->udata = n++; + net_by_udata.push_back(net.second.get()); + } + for (auto ®ion : ctx->region) { + Region *r = region.second.get(); + BoundingBox bb; + if (r->constr_bels) { + bb.x0 = std::numeric_limits::max(); + bb.x1 = std::numeric_limits::min(); + bb.y0 = std::numeric_limits::max(); + bb.y1 = std::numeric_limits::min(); + for (auto bel : r->bels) { + Loc loc = ctx->getBelLocation(bel); + bb.x0 = std::min(bb.x0, loc.x); + bb.x1 = std::max(bb.x1, loc.x); + bb.y0 = std::min(bb.y0, loc.y); + bb.y1 = std::max(bb.y1, loc.y); + } + } else { + bb.x0 = 0; + bb.y0 = 0; + bb.x1 = max_x; + bb.y1 = max_y; + } + region_bounds[r->name] = bb; + } + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->cluster == ClusterId()) + continue; + cluster2cell[ci->cluster].push_back(ci); + } + } + + ~SAPlacer() + { + for (auto &net : ctx->nets) + net.second->udata = old_udata[net.second->udata]; + } + + bool place(bool refine = false) + { + log_break(); + + ScopeLock lock(ctx); + + size_t placed_cells = 0; + std::vector autoplaced; + std::vector chain_basis; + if (!refine) { + // Initial constraints placer + for (auto &cell_entry : ctx->cells) { + CellInfo *cell = cell_entry.second.get(); + auto loc = cell->attrs.find(ctx->id("BEL")); + if (loc != cell->attrs.end()) { + std::string loc_name = loc->second.as_string(); + BelId bel = ctx->getBelByNameStr(loc_name); + if (bel == BelId()) { + log_error("No Bel named \'%s\' located for " + "this chip (processing BEL attribute on \'%s\')\n", + loc_name.c_str(), cell->name.c_str(ctx)); + } + + if (!ctx->isValidBelForCellType(cell->type, bel)) { + IdString bel_type = ctx->getBelType(bel); + log_error("Bel \'%s\' of type \'%s\' does not match cell " + "\'%s\' of type \'%s\'\n", + loc_name.c_str(), bel_type.c_str(ctx), cell->name.c_str(ctx), cell->type.c_str(ctx)); + } + auto bound_cell = ctx->getBoundBelCell(bel); + if (bound_cell) { + log_error( + "Cell \'%s\' cannot be bound to bel \'%s\' since it is already bound to cell \'%s\'\n", + cell->name.c_str(ctx), loc_name.c_str(), bound_cell->name.c_str(ctx)); + } + + ctx->bindBel(bel, cell, STRENGTH_USER); + if (!ctx->isBelLocationValid(bel)) { + IdString bel_type = ctx->getBelType(bel); + log_error("Bel \'%s\' of type \'%s\' is not valid for cell " + "\'%s\' of type \'%s\'\n", + loc_name.c_str(), bel_type.c_str(ctx), cell->name.c_str(ctx), cell->type.c_str(ctx)); + } + locked_bels.insert(bel); + placed_cells++; + } + } + int constr_placed_cells = placed_cells; + log_info("Placed %d cells based on constraints.\n", int(placed_cells)); + ctx->yield(); + + // Sort to-place cells for deterministic initial placement + + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->bel == BelId()) { + autoplaced.push_back(cell.second.get()); + } + } + std::sort(autoplaced.begin(), autoplaced.end(), [](CellInfo *a, CellInfo *b) { return a->name < b->name; }); + ctx->shuffle(autoplaced); + auto iplace_start = std::chrono::high_resolution_clock::now(); + // Place cells randomly initially + log_info("Creating initial placement for remaining %d cells.\n", int(autoplaced.size())); + + for (auto cell : autoplaced) { + place_initial(cell); + placed_cells++; + if ((placed_cells - constr_placed_cells) % 500 == 0) + log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), + int(autoplaced.size())); + } + if ((placed_cells - constr_placed_cells) % 500 != 0) + log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), + int(autoplaced.size())); + if (cfg.budgetBased && cfg.slack_redist_iter > 0) + assign_budget(ctx); + ctx->yield(); + auto iplace_end = std::chrono::high_resolution_clock::now(); + log_info("Initial placement time %.02fs\n", + std::chrono::duration(iplace_end - iplace_start).count()); + log_info("Running simulated annealing placer.\n"); + } else { + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->belStrength > STRENGTH_STRONG) { + continue; + } else if (ci->cluster != ClusterId()) { + if (ctx->getClusterRootCell(ci->cluster) == ci) + chain_basis.push_back(ci); + else + continue; + } else { + autoplaced.push_back(ci); + } + } + require_legal = false; + diameter = 3; + log_info("Running simulated annealing placer for refinement.\n"); + } + auto saplace_start = std::chrono::high_resolution_clock::now(); + + // Invoke timing analysis to obtain criticalities + tmg.setup_only = true; + if (!cfg.budgetBased) + tmg.setup(); + + // Calculate costs after initial placement + setup_costs(); + moveChange.init(this); + curr_wirelen_cost = total_wirelen_cost(); + curr_timing_cost = total_timing_cost(); + last_wirelen_cost = curr_wirelen_cost; + last_timing_cost = curr_timing_cost; + + if (cfg.netShareWeight > 0) + setup_nets_by_tile(); + + wirelen_t avg_wirelen = curr_wirelen_cost; + wirelen_t min_wirelen = curr_wirelen_cost; + + int n_no_progress = 0; + temp = refine ? 1e-7 : cfg.startTemp; + + // Main simulated annealing loop + for (int iter = 1;; iter++) { + n_move = n_accept = 0; + improved = false; + + if (iter % 5 == 0 || iter == 1) + log_info(" at iteration #%d: temp = %f, timing cost = " + "%.0f, wirelen = %.0f\n", + iter, temp, double(curr_timing_cost), double(curr_wirelen_cost)); + + for (int m = 0; m < 15; ++m) { + // Loop through all automatically placed cells + for (auto cell : autoplaced) { + // Find another random Bel for this cell + BelId try_bel = random_bel_for_cell(cell); + // If valid, try and swap to a new position and see if + // the new position is valid/worthwhile + if (try_bel != BelId() && try_bel != cell->bel) + try_swap_position(cell, try_bel); + } + // Also try swapping chains, if applicable + for (auto cb : chain_basis) { + Loc chain_base_loc = ctx->getBelLocation(cb->bel); + BelId try_base = random_bel_for_cell(cb, chain_base_loc.z); + if (try_base != BelId() && try_base != cb->bel) + try_swap_chain(cb, try_base); + } + } + + if (ctx->debug) { + // Verify correctness of incremental wirelen updates + for (size_t i = 0; i < net_bounds.size(); i++) { + auto net = net_by_udata[i]; + if (ignore_net(net)) + continue; + auto &incr = net_bounds.at(i), gold = get_net_bounds(net); + NPNR_ASSERT(incr.x0 == gold.x0); + NPNR_ASSERT(incr.x1 == gold.x1); + NPNR_ASSERT(incr.y0 == gold.y0); + NPNR_ASSERT(incr.y1 == gold.y1); + NPNR_ASSERT(incr.nx0 == gold.nx0); + NPNR_ASSERT(incr.nx1 == gold.nx1); + NPNR_ASSERT(incr.ny0 == gold.ny0); + NPNR_ASSERT(incr.ny1 == gold.ny1); + } + } + + if (curr_wirelen_cost < min_wirelen) { + min_wirelen = curr_wirelen_cost; + improved = true; + } + + // Heuristic to improve placement on the 8k + if (improved) + n_no_progress = 0; + else + n_no_progress++; + + if (temp <= 1e-7 && n_no_progress >= (refine ? 1 : 5)) { + log_info(" at iteration #%d: temp = %f, timing cost = " + "%.0f, wirelen = %.0f \n", + iter, temp, double(curr_timing_cost), double(curr_wirelen_cost)); + break; + } + + double Raccept = double(n_accept) / double(n_move); + + int M = std::max(max_x, max_y) + 1; + + if (ctx->verbose) + log("iter #%d: temp = %f, timing cost = " + "%.0f, wirelen = %.0f, dia = %d, Ra = %.02f \n", + iter, temp, double(curr_timing_cost), double(curr_wirelen_cost), diameter, Raccept); + + if (curr_wirelen_cost < 0.95 * avg_wirelen && curr_wirelen_cost > 0) { + avg_wirelen = 0.8 * avg_wirelen + 0.2 * curr_wirelen_cost; + } else { + double diam_next = diameter * (1.0 - 0.44 + Raccept); + diameter = std::max(1, std::min(M, int(diam_next + 0.5))); + if (Raccept > 0.96) { + temp *= 0.5; + } else if (Raccept > 0.8) { + temp *= 0.9; + } else if (Raccept > 0.15 && diameter > 1) { + temp *= 0.95; + } else { + temp *= 0.8; + } + } + // Once cooled below legalise threshold, run legalisation and start requiring + // legal moves only + if (diameter < legalise_dia && require_legal) { + if (legalise_relative_constraints(ctx)) { + // Only increase temperature if something was moved + autoplaced.clear(); + chain_basis.clear(); + for (auto &cell : ctx->cells) { + if (cell.second->belStrength <= STRENGTH_STRONG && cell.second->cluster != ClusterId() && + ctx->getClusterRootCell(cell.second->cluster) == cell.second.get()) + chain_basis.push_back(cell.second.get()); + else if (cell.second->belStrength < STRENGTH_STRONG) + autoplaced.push_back(cell.second.get()); + } + // temp = post_legalise_temp; + // diameter = std::min(M, diameter * post_legalise_dia_scale); + ctx->shuffle(autoplaced); + + // Legalisation is a big change so force a slack redistribution here + if (cfg.slack_redist_iter > 0 && cfg.budgetBased) + assign_budget(ctx, true /* quiet */); + } + require_legal = false; + } else if (cfg.budgetBased && cfg.slack_redist_iter > 0 && iter % cfg.slack_redist_iter == 0) { + assign_budget(ctx, true /* quiet */); + } + + // Invoke timing analysis to obtain criticalities + if (!cfg.budgetBased && cfg.timing_driven) + tmg.run(); + // Need to rebuild costs after criticalities change + setup_costs(); + // Reset incremental bounds + moveChange.reset(this); + moveChange.new_net_bounds = net_bounds; + + // Recalculate total metric entirely to avoid rounding errors + // accumulating over time + curr_wirelen_cost = total_wirelen_cost(); + curr_timing_cost = total_timing_cost(); + last_wirelen_cost = curr_wirelen_cost; + last_timing_cost = curr_timing_cost; + // Let the UI show visualization updates. + ctx->yield(); + } + + auto saplace_end = std::chrono::high_resolution_clock::now(); + log_info("SA placement time %.02fs\n", std::chrono::duration(saplace_end - saplace_start).count()); + + // Final post-placement validity check + ctx->yield(); + for (auto bel : ctx->getBels()) { + CellInfo *cell = ctx->getBoundBelCell(bel); + if (!ctx->isBelLocationValid(bel)) { + std::string cell_text = "no cell"; + if (cell != nullptr) + cell_text = std::string("cell '") + ctx->nameOf(cell) + "'"; + if (ctx->force) { + log_warning("post-placement validity check failed for Bel '%s' " + "(%s)\n", + ctx->nameOfBel(bel), cell_text.c_str()); + } else { + log_error("post-placement validity check failed for Bel '%s' " + "(%s)\n", + ctx->nameOfBel(bel), cell_text.c_str()); + } + } + } + timing_analysis(ctx); + + return true; + } + + private: + // Initial random placement + void place_initial(CellInfo *cell) + { + bool all_placed = false; + int iters = 25; + while (!all_placed) { + BelId best_bel = BelId(); + uint64_t best_score = std::numeric_limits::max(), + best_ripup_score = std::numeric_limits::max(); + CellInfo *ripup_target = nullptr; + BelId ripup_bel = BelId(); + if (cell->bel != BelId()) { + ctx->unbindBel(cell->bel); + } + IdString targetType = cell->type; + + auto proc_bel = [&](BelId bel) { + if (ctx->isValidBelForCellType(targetType, bel)) { + if (ctx->checkBelAvail(bel)) { + uint64_t score = ctx->rng64(); + if (score <= best_score) { + best_score = score; + best_bel = bel; + } + } else { + uint64_t score = ctx->rng64(); + CellInfo *bound_cell = ctx->getBoundBelCell(bel); + if (score <= best_ripup_score && bound_cell->belStrength < STRENGTH_STRONG) { + best_ripup_score = score; + ripup_target = bound_cell; + ripup_bel = bel; + } + } + } + }; + + if (cell->region != nullptr && cell->region->constr_bels) { + for (auto bel : cell->region->bels) { + proc_bel(bel); + } + } else { + for (auto bel : ctx->getBels()) { + proc_bel(bel); + } + } + + if (best_bel == BelId()) { + if (iters == 0 || ripup_bel == BelId()) + log_error("failed to place cell '%s' of type '%s'\n", cell->name.c_str(ctx), cell->type.c_str(ctx)); + --iters; + ctx->unbindBel(ripup_target->bel); + best_bel = ripup_bel; + } else { + ripup_target = nullptr; + all_placed = true; + } + ctx->bindBel(best_bel, cell, STRENGTH_WEAK); + + if (!ctx->isBelLocationValid(best_bel)) { + ctx->unbindBel(best_bel); + if (ripup_target != nullptr) { + ctx->bindBel(best_bel, ripup_target, STRENGTH_WEAK); + } + all_placed = false; + continue; + } + + // Back annotate location + cell->attrs[ctx->id("BEL")] = ctx->getBelName(cell->bel).str(ctx); + cell = ripup_target; + } + } + + // Attempt a SA position swap, return true on success or false on failure + bool try_swap_position(CellInfo *cell, BelId newBel) + { + static const double epsilon = 1e-20; + moveChange.reset(this); + if (!require_legal && cell->cluster != ClusterId()) + return false; + BelId oldBel = cell->bel; + CellInfo *other_cell = ctx->getBoundBelCell(newBel); + if (!require_legal && other_cell != nullptr && + (other_cell->cluster != ClusterId() || other_cell->belStrength > STRENGTH_WEAK)) { + return false; + } + int old_dist = get_constraints_distance(ctx, cell); + int new_dist; + if (other_cell != nullptr) + old_dist += get_constraints_distance(ctx, other_cell); + double delta = 0; + + if (!ctx->isValidBelForCellType(cell->type, newBel)) { + return false; + } + if (other_cell != nullptr && !ctx->isValidBelForCellType(other_cell->type, oldBel)) { + return false; + } + + int net_delta_score = 0; + if (cfg.netShareWeight > 0) + net_delta_score += update_nets_by_tile(cell, ctx->getBelLocation(cell->bel), ctx->getBelLocation(newBel)); + + ctx->unbindBel(oldBel); + if (other_cell != nullptr) { + ctx->unbindBel(newBel); + } + + ctx->bindBel(newBel, cell, STRENGTH_WEAK); + + if (other_cell != nullptr) { + ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK); + if (cfg.netShareWeight > 0) + net_delta_score += + update_nets_by_tile(other_cell, ctx->getBelLocation(newBel), ctx->getBelLocation(oldBel)); + } + + add_move_cell(moveChange, cell, oldBel); + + if (other_cell != nullptr) { + add_move_cell(moveChange, other_cell, newBel); + } + + // Always check both the new and old locations; as in some cases of dedicated routing ripping up a cell can deny + // use of a dedicated path and thus make a site illegal + if (!ctx->isBelLocationValid(newBel) || !ctx->isBelLocationValid(oldBel)) { + ctx->unbindBel(newBel); + if (other_cell != nullptr) + ctx->unbindBel(oldBel); + goto swap_fail; + } + + // Recalculate metrics for all nets touched by the perturbation + compute_cost_changes(moveChange); + + new_dist = get_constraints_distance(ctx, cell); + if (other_cell != nullptr) + new_dist += get_constraints_distance(ctx, other_cell); + delta = lambda * (moveChange.timing_delta / std::max(last_timing_cost, epsilon)) + + (1 - lambda) * (double(moveChange.wirelen_delta) / std::max(last_wirelen_cost, epsilon)); + delta += (cfg.constraintWeight / temp) * (new_dist - old_dist) / last_wirelen_cost; + if (cfg.netShareWeight > 0) + delta += -cfg.netShareWeight * (net_delta_score / std::max(total_net_share, epsilon)); + n_move++; + // SA acceptance criteria + if (delta < 0 || (temp > 1e-8 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) { + n_accept++; + } else { + if (other_cell != nullptr) + ctx->unbindBel(oldBel); + ctx->unbindBel(newBel); + goto swap_fail; + } + commit_cost_changes(moveChange); +#if 0 + log_info("swap %s -> %s\n", cell->name.c_str(ctx), ctx->nameOfBel(newBel)); + if (other_cell != nullptr) + log_info("swap %s -> %s\n", other_cell->name.c_str(ctx), ctx->nameOfBel(oldBel)); +#endif + return true; + swap_fail: + ctx->bindBel(oldBel, cell, STRENGTH_WEAK); + if (other_cell != nullptr) { + ctx->bindBel(newBel, other_cell, STRENGTH_WEAK); + if (cfg.netShareWeight > 0) + update_nets_by_tile(other_cell, ctx->getBelLocation(oldBel), ctx->getBelLocation(newBel)); + } + if (cfg.netShareWeight > 0) + update_nets_by_tile(cell, ctx->getBelLocation(newBel), ctx->getBelLocation(oldBel)); + return false; + } + + // Swap the Bel of a cell with another, return the original location + BelId swap_cell_bels(CellInfo *cell, BelId newBel) + { + BelId oldBel = cell->bel; +#if 0 + log_info("%s old: %s new: %s\n", cell->name.c_str(ctx), ctx->nameOfBel(cell->bel), ctx->nameOfBel(newBel)); +#endif + CellInfo *bound = ctx->getBoundBelCell(newBel); + if (bound != nullptr) + ctx->unbindBel(newBel); + ctx->unbindBel(oldBel); + ctx->bindBel(newBel, cell, (cell->cluster != ClusterId()) ? STRENGTH_STRONG : STRENGTH_WEAK); + if (bound != nullptr) { + ctx->bindBel(oldBel, bound, (bound->cluster != ClusterId()) ? STRENGTH_STRONG : STRENGTH_WEAK); + if (cfg.netShareWeight > 0) + update_nets_by_tile(bound, ctx->getBelLocation(newBel), ctx->getBelLocation(oldBel)); + } + if (cfg.netShareWeight > 0) + update_nets_by_tile(cell, ctx->getBelLocation(oldBel), ctx->getBelLocation(newBel)); + return oldBel; + } + + // Attempt to swap a chain with a non-chain + bool try_swap_chain(CellInfo *cell, BelId newBase) + { + std::vector> cell_rel; + dict moved_cells; + double delta = 0; + int orig_share_cost = total_net_share; + moveChange.reset(this); +#if CHAIN_DEBUG + log_info("finding cells for chain swap %s\n", cell->name.c_str(ctx)); +#endif + std::queue> displaced_clusters; + displaced_clusters.emplace(cell->cluster, newBase); + while (!displaced_clusters.empty()) { + std::vector> dest_bels; + auto cursor = displaced_clusters.front(); +#if CHAIN_DEBUG + log_info("%d Cluster %s\n", __LINE__, cursor.first.c_str(ctx)); +#endif + displaced_clusters.pop(); + if (!ctx->getClusterPlacement(cursor.first, cursor.second, dest_bels)) + goto swap_fail; + for (const auto &db : dest_bels) { + // Ensure the cluster is ripped up + if (db.first->bel != BelId()) { + moved_cells[db.first->name] = db.first->bel; +#if CHAIN_DEBUG + log_info("%d unbind %s\n", __LINE__, ctx->nameOfBel(db.first->bel)); +#endif + ctx->unbindBel(db.first->bel); + } + } + for (const auto &db : dest_bels) { + CellInfo *bound = ctx->getBoundBelCell(db.second); + BelId old_bel = moved_cells.at(db.first->name); + if (!ctx->checkBelAvail(old_bel) && bound != nullptr) { + // Simple swap no longer possible + goto swap_fail; + } + if (bound != nullptr) { + if (moved_cells.count(bound->name)) { + // Don't move a cell multiple times in the same go + goto swap_fail; + } else if (bound->belStrength > STRENGTH_STRONG) { + goto swap_fail; + } else if (bound->cluster != ClusterId()) { + // Displace the entire cluster + Loc old_loc = ctx->getBelLocation(old_bel); + Loc bound_loc = ctx->getBelLocation(bound->bel); + Loc root_loc = ctx->getBelLocation(ctx->getClusterRootCell(bound->cluster)->bel); + BelId new_root = ctx->getBelByLocation(Loc(old_loc.x + (root_loc.x - bound_loc.x), + old_loc.y + (root_loc.y - bound_loc.y), + old_loc.z + (root_loc.z - bound_loc.z))); + if (new_root == BelId()) + goto swap_fail; + for (auto cluster_cell : cluster2cell.at(bound->cluster)) { + moved_cells[cluster_cell->name] = cluster_cell->bel; +#if CHAIN_DEBUG + log_info("%d unbind %s\n", __LINE__, ctx->nameOfBel(cluster_cell->bel)); +#endif + ctx->unbindBel(cluster_cell->bel); + } + displaced_clusters.emplace(bound->cluster, new_root); + } else { + // Just a single cell to move + moved_cells[bound->name] = bound->bel; +#if CHAIN_DEBUG + log_info("%d unbind %s\n", __LINE__, ctx->nameOfBel(bound->bel)); + log_info("%d bind %s %s\n", __LINE__, ctx->nameOfBel(old_bel), ctx->nameOf(bound)); +#endif + ctx->unbindBel(bound->bel); + ctx->bindBel(old_bel, bound, STRENGTH_WEAK); + } + } else if (!ctx->checkBelAvail(db.second)) { + goto swap_fail; + } + // All those shenanigans should now mean the target bel is free to use +#if CHAIN_DEBUG + log_info("%d bind %s %s\n", __LINE__, ctx->nameOfBel(db.second), ctx->nameOf(db.first)); +#endif + ctx->bindBel(db.second, db.first, STRENGTH_WEAK); + } + } + + for (const auto &mm : moved_cells) { + CellInfo *cell = ctx->cells.at(mm.first).get(); + add_move_cell(moveChange, cell, moved_cells.at(cell->name)); + if (cfg.netShareWeight > 0) + update_nets_by_tile(cell, ctx->getBelLocation(moved_cells.at(cell->name)), + ctx->getBelLocation(cell->bel)); + if (!ctx->isBelLocationValid(cell->bel) || !cell->testRegion(cell->bel)) + goto swap_fail; + } +#if CHAIN_DEBUG + log_info("legal chain swap %s\n", cell->name.c_str(ctx)); +#endif + compute_cost_changes(moveChange); + delta = lambda * (moveChange.timing_delta / last_timing_cost) + + (1 - lambda) * (double(moveChange.wirelen_delta) / last_wirelen_cost); + if (cfg.netShareWeight > 0) { + delta += + cfg.netShareWeight * (orig_share_cost - total_net_share) / std::max(total_net_share, 1e-20); + } + n_move++; + // SA acceptance criteria + if (delta < 0 || (temp > 1e-8 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) { + n_accept++; +#if CHAIN_DEBUG + log_info("accepted chain swap %s\n", cell->name.c_str(ctx)); +#endif + } else { + goto swap_fail; + } + commit_cost_changes(moveChange); + return true; + swap_fail: +#if CHAIN_DEBUG + log_info("Swap failed\n"); +#endif + for (auto cell_pair : moved_cells) { + CellInfo *cell = ctx->cells.at(cell_pair.first).get(); + if (cell->bel != BelId()) { +#if CHAIN_DEBUG + log_info("%d unbind %s\n", __LINE__, ctx->nameOfBel(cell->bel)); +#endif + ctx->unbindBel(cell->bel); + } + } + for (auto cell_pair : moved_cells) { + CellInfo *cell = ctx->cells.at(cell_pair.first).get(); +#if CHAIN_DEBUG + log_info("%d bind %s %s\n", __LINE__, ctx->nameOfBel(cell_pair.second), cell->name.c_str(ctx)); +#endif + ctx->bindBel(cell_pair.second, cell, STRENGTH_WEAK); + } + return false; + } + + // Find a random Bel of the correct type for a cell, within the specified + // diameter + BelId random_bel_for_cell(CellInfo *cell, int force_z = -1) + { + IdString targetType = cell->type; + Loc curr_loc = ctx->getBelLocation(cell->bel); + int count = 0; + + int dx = diameter, dy = diameter; + if (cell->region != nullptr && cell->region->constr_bels) { + dx = std::min(cfg.hpwl_scale_x * diameter, + (region_bounds[cell->region->name].x1 - region_bounds[cell->region->name].x0) + 1); + dy = std::min(cfg.hpwl_scale_y * diameter, + (region_bounds[cell->region->name].y1 - region_bounds[cell->region->name].y0) + 1); + // Clamp location to within bounds + curr_loc.x = std::max(region_bounds[cell->region->name].x0, curr_loc.x); + curr_loc.x = std::min(region_bounds[cell->region->name].x1, curr_loc.x); + curr_loc.y = std::max(region_bounds[cell->region->name].y0, curr_loc.y); + curr_loc.y = std::min(region_bounds[cell->region->name].y1, curr_loc.y); + } + + FastBels::FastBelsData *bel_data; + auto type_cnt = fast_bels.getBelsForCellType(targetType, &bel_data); + + while (true) { + int nx = ctx->rng(2 * dx + 1) + std::max(curr_loc.x - dx, 0); + int ny = ctx->rng(2 * dy + 1) + std::max(curr_loc.y - dy, 0); + if (cfg.minBelsForGridPick >= 0 && type_cnt < cfg.minBelsForGridPick) + nx = ny = 0; + if (nx >= int(bel_data->size())) + continue; + if (ny >= int(bel_data->at(nx).size())) + continue; + const auto &fb = bel_data->at(nx).at(ny); + if (fb.size() == 0) + continue; + BelId bel = fb.at(ctx->rng(int(fb.size()))); + if (force_z != -1) { + Loc loc = ctx->getBelLocation(bel); + if (loc.z != force_z) + continue; + } + if (!cell->testRegion(bel)) + continue; + if (locked_bels.find(bel) != locked_bels.end()) + continue; + count++; + return bel; + } + } + + // Return true if a net is to be entirely ignored + inline bool ignore_net(NetInfo *net) + { + return net->driver.cell == nullptr || net->driver.cell->bel == BelId() || + ctx->getBelGlobalBuf(net->driver.cell->bel); + } + + // Get the bounding box for a net + inline BoundingBox get_net_bounds(NetInfo *net) + { + BoundingBox bb; + NPNR_ASSERT(net->driver.cell != nullptr); + Loc dloc = ctx->getBelLocation(net->driver.cell->bel); + bb.x0 = dloc.x; + bb.x1 = dloc.x; + bb.y0 = dloc.y; + bb.y1 = dloc.y; + bb.nx0 = 1; + bb.nx1 = 1; + bb.ny0 = 1; + bb.ny1 = 1; + for (auto user : net->users) { + if (user.cell->bel == BelId()) + continue; + Loc uloc = ctx->getBelLocation(user.cell->bel); + if (bb.x0 == uloc.x) + ++bb.nx0; + else if (uloc.x < bb.x0) { + bb.x0 = uloc.x; + bb.nx0 = 1; + } + if (bb.x1 == uloc.x) + ++bb.nx1; + else if (uloc.x > bb.x1) { + bb.x1 = uloc.x; + bb.nx1 = 1; + } + if (bb.y0 == uloc.y) + ++bb.ny0; + else if (uloc.y < bb.y0) { + bb.y0 = uloc.y; + bb.ny0 = 1; + } + if (bb.y1 == uloc.y) + ++bb.ny1; + else if (uloc.y > bb.y1) { + bb.y1 = uloc.y; + bb.ny1 = 1; + } + } + + return bb; + } + + // Get the timing cost for an arc of a net + inline double get_timing_cost(NetInfo *net, const PortRef &user) + { + int cc; + if (net->driver.cell == nullptr) + return 0; + if (ctx->getPortTimingClass(net->driver.cell, net->driver.port, cc) == TMG_IGNORE) + return 0; + if (cfg.budgetBased) { + double delay = ctx->getDelayNS(ctx->predictArcDelay(net, user)); + return std::min(10.0, std::exp(delay - ctx->getDelayNS(user.budget) / 10)); + } else { + float crit = tmg.get_criticality(CellPortKey(user)); + double delay = ctx->getDelayNS(ctx->predictArcDelay(net, user)); + return delay * std::pow(crit, crit_exp); + } + } + + // Set up the cost maps + void setup_costs() + { + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (ignore_net(ni)) + continue; + net_bounds[ni->udata] = get_net_bounds(ni); + if (cfg.timing_driven && int(ni->users.entries()) < cfg.timingFanoutThresh) + for (auto usr : ni->users.enumerate()) + net_arc_tcost[ni->udata][usr.index.idx()] = get_timing_cost(ni, usr.value); + } + } + + // Get the total wiring cost for the design + wirelen_t total_wirelen_cost() + { + wirelen_t cost = 0; + for (const auto &net : net_bounds) + cost += net.hpwl(cfg); + return cost; + } + + // Get the total timing cost for the design + double total_timing_cost() + { + double cost = 0; + for (const auto &net : net_arc_tcost) { + for (auto arc_cost : net) { + cost += arc_cost; + } + } + return cost; + } + + // Cost-change-related data for a move + struct MoveChangeData + { + + enum BoundChangeType + { + NO_CHANGE, + CELL_MOVED_INWARDS, + CELL_MOVED_OUTWARDS, + FULL_RECOMPUTE + }; + + std::vector bounds_changed_nets_x, bounds_changed_nets_y; + std::vector>> changed_arcs; + + std::vector already_bounds_changed_x, already_bounds_changed_y; + std::vector> already_changed_arcs; + + std::vector new_net_bounds; + std::vector>, double>> new_arc_costs; + + wirelen_t wirelen_delta = 0; + double timing_delta = 0; + + void init(SAPlacer *p) + { + already_bounds_changed_x.resize(p->ctx->nets.size()); + already_bounds_changed_y.resize(p->ctx->nets.size()); + already_changed_arcs.resize(p->ctx->nets.size()); + for (auto &net : p->ctx->nets) { + already_changed_arcs.at(net.second->udata).resize(net.second->users.capacity()); + } + new_net_bounds = p->net_bounds; + } + + void reset(SAPlacer *p) + { + for (auto bc : bounds_changed_nets_x) { + new_net_bounds[bc] = p->net_bounds[bc]; + already_bounds_changed_x[bc] = NO_CHANGE; + } + for (auto bc : bounds_changed_nets_y) { + new_net_bounds[bc] = p->net_bounds[bc]; + already_bounds_changed_y[bc] = NO_CHANGE; + } + for (const auto &tc : changed_arcs) + already_changed_arcs[tc.first][tc.second.idx()] = false; + bounds_changed_nets_x.clear(); + bounds_changed_nets_y.clear(); + changed_arcs.clear(); + new_arc_costs.clear(); + wirelen_delta = 0; + timing_delta = 0; + } + + } moveChange; + + void add_move_cell(MoveChangeData &mc, CellInfo *cell, BelId old_bel) + { + Loc curr_loc = ctx->getBelLocation(cell->bel); + Loc old_loc = ctx->getBelLocation(old_bel); + // Check net bounds + for (const auto &port : cell->ports) { + NetInfo *pn = port.second.net; + if (pn == nullptr) + continue; + if (ignore_net(pn)) + continue; + BoundingBox &curr_bounds = mc.new_net_bounds[pn->udata]; + // Incremental bounding box updates + // Note that everything other than full updates are applied immediately rather than being queued, + // so further updates to the same net in the same move are dealt with correctly. + // If a full update is already queued, this can be considered a no-op + if (mc.already_bounds_changed_x[pn->udata] != MoveChangeData::FULL_RECOMPUTE) { + // Bounds x0 + if (curr_loc.x < curr_bounds.x0) { + // Further out than current bounds x0 + curr_bounds.x0 = curr_loc.x; + curr_bounds.nx0 = 1; + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) { + // Checking already_bounds_changed_x ensures that each net is only added once + // to bounds_changed_nets, lest we add its HPWL change multiple times skewing the + // overall cost change + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_x.push_back(pn->udata); + } + } else if (curr_loc.x == curr_bounds.x0 && old_loc.x > curr_bounds.x0) { + curr_bounds.nx0++; + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) { + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_x.push_back(pn->udata); + } + } else if (old_loc.x == curr_bounds.x0 && curr_loc.x > curr_bounds.x0) { + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) + mc.bounds_changed_nets_x.push_back(pn->udata); + if (curr_bounds.nx0 == 1) { + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::FULL_RECOMPUTE; + } else { + curr_bounds.nx0--; + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_INWARDS; + } + } + + // Bounds x1 + if (curr_loc.x > curr_bounds.x1) { + // Further out than current bounds x1 + curr_bounds.x1 = curr_loc.x; + curr_bounds.nx1 = 1; + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) { + // Checking already_bounds_changed_x ensures that each net is only added once + // to bounds_changed_nets, lest we add its HPWL change multiple times skewing the + // overall cost change + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_x.push_back(pn->udata); + } + } else if (curr_loc.x == curr_bounds.x1 && old_loc.x < curr_bounds.x1) { + curr_bounds.nx1++; + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) { + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_x.push_back(pn->udata); + } + } else if (old_loc.x == curr_bounds.x1 && curr_loc.x < curr_bounds.x1) { + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) + mc.bounds_changed_nets_x.push_back(pn->udata); + if (curr_bounds.nx1 == 1) { + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::FULL_RECOMPUTE; + } else { + curr_bounds.nx1--; + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_INWARDS; + } + } + } + if (mc.already_bounds_changed_y[pn->udata] != MoveChangeData::FULL_RECOMPUTE) { + // Bounds y0 + if (curr_loc.y < curr_bounds.y0) { + // Further out than current bounds y0 + curr_bounds.y0 = curr_loc.y; + curr_bounds.ny0 = 1; + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) { + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_y.push_back(pn->udata); + } + } else if (curr_loc.y == curr_bounds.y0 && old_loc.y > curr_bounds.y0) { + curr_bounds.ny0++; + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) { + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_y.push_back(pn->udata); + } + } else if (old_loc.y == curr_bounds.y0 && curr_loc.y > curr_bounds.y0) { + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) + mc.bounds_changed_nets_y.push_back(pn->udata); + if (curr_bounds.ny0 == 1) { + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::FULL_RECOMPUTE; + } else { + curr_bounds.ny0--; + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_INWARDS; + } + } + + // Bounds y1 + if (curr_loc.y > curr_bounds.y1) { + // Further out than current bounds y1 + curr_bounds.y1 = curr_loc.y; + curr_bounds.ny1 = 1; + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) { + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_y.push_back(pn->udata); + } + } else if (curr_loc.y == curr_bounds.y1 && old_loc.y < curr_bounds.y1) { + curr_bounds.ny1++; + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) { + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_y.push_back(pn->udata); + } + } else if (old_loc.y == curr_bounds.y1 && curr_loc.y < curr_bounds.y1) { + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) + mc.bounds_changed_nets_y.push_back(pn->udata); + if (curr_bounds.ny1 == 1) { + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::FULL_RECOMPUTE; + } else { + curr_bounds.ny1--; + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_INWARDS; + } + } + } + + if (cfg.timing_driven && int(pn->users.entries()) < cfg.timingFanoutThresh) { + // Output ports - all arcs change timing + if (port.second.type == PORT_OUT) { + int cc; + TimingPortClass cls = ctx->getPortTimingClass(cell, port.first, cc); + if (cls != TMG_IGNORE) + for (auto usr : pn->users.enumerate()) + if (!mc.already_changed_arcs[pn->udata][usr.index.idx()]) { + mc.changed_arcs.emplace_back(std::make_pair(pn->udata, usr.index)); + mc.already_changed_arcs[pn->udata][usr.index.idx()] = true; + } + } else if (port.second.type == PORT_IN) { + auto usr_idx = port.second.user_idx; + if (!mc.already_changed_arcs[pn->udata][usr_idx.idx()]) { + mc.changed_arcs.emplace_back(std::make_pair(pn->udata, usr_idx)); + mc.already_changed_arcs[pn->udata][usr_idx.idx()] = true; + } + } + } + } + } + + void compute_cost_changes(MoveChangeData &md) + { + for (const auto &bc : md.bounds_changed_nets_x) { + if (md.already_bounds_changed_x[bc] == MoveChangeData::FULL_RECOMPUTE) + md.new_net_bounds[bc] = get_net_bounds(net_by_udata[bc]); + } + for (const auto &bc : md.bounds_changed_nets_y) { + if (md.already_bounds_changed_x[bc] != MoveChangeData::FULL_RECOMPUTE && + md.already_bounds_changed_y[bc] == MoveChangeData::FULL_RECOMPUTE) + md.new_net_bounds[bc] = get_net_bounds(net_by_udata[bc]); + } + + for (const auto &bc : md.bounds_changed_nets_x) + md.wirelen_delta += md.new_net_bounds[bc].hpwl(cfg) - net_bounds[bc].hpwl(cfg); + for (const auto &bc : md.bounds_changed_nets_y) + if (md.already_bounds_changed_x[bc] == MoveChangeData::NO_CHANGE) + md.wirelen_delta += md.new_net_bounds[bc].hpwl(cfg) - net_bounds[bc].hpwl(cfg); + + if (cfg.timing_driven) { + for (const auto &tc : md.changed_arcs) { + double old_cost = net_arc_tcost.at(tc.first).at(tc.second.idx()); + double new_cost = + get_timing_cost(net_by_udata.at(tc.first), net_by_udata.at(tc.first)->users.at(tc.second)); + md.new_arc_costs.emplace_back(std::make_pair(tc, new_cost)); + md.timing_delta += (new_cost - old_cost); + md.already_changed_arcs[tc.first][tc.second.idx()] = false; + } + } + } + + void commit_cost_changes(MoveChangeData &md) + { + for (const auto &bc : md.bounds_changed_nets_x) + net_bounds[bc] = md.new_net_bounds[bc]; + for (const auto &bc : md.bounds_changed_nets_y) + net_bounds[bc] = md.new_net_bounds[bc]; + for (const auto &tc : md.new_arc_costs) + net_arc_tcost[tc.first.first].at(tc.first.second.idx()) = tc.second; + curr_wirelen_cost += md.wirelen_delta; + curr_timing_cost += md.timing_delta; + } + + // Simple routeability driven placement + const int large_cell_thresh = 50; + int total_net_share = 0; + std::vector>> nets_by_tile; + void setup_nets_by_tile() + { + total_net_share = 0; + nets_by_tile.resize(max_x + 1, std::vector>(max_y + 1)); + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (int(ci->ports.size()) > large_cell_thresh) + continue; + Loc loc = ctx->getBelLocation(ci->bel); + auto &nbt = nets_by_tile.at(loc.x).at(loc.y); + for (const auto &port : ci->ports) { + if (port.second.net == nullptr) + continue; + if (port.second.net->driver.cell == nullptr || ctx->getBelGlobalBuf(port.second.net->driver.cell->bel)) + continue; + int &s = nbt[port.second.net->name]; + if (s > 0) + ++total_net_share; + ++s; + } + } + } + + int update_nets_by_tile(CellInfo *ci, Loc old_loc, Loc new_loc) + { + if (int(ci->ports.size()) > large_cell_thresh) + return 0; + int loss = 0, gain = 0; + auto &nbt_old = nets_by_tile.at(old_loc.x).at(old_loc.y); + auto &nbt_new = nets_by_tile.at(new_loc.x).at(new_loc.y); + + for (const auto &port : ci->ports) { + if (port.second.net == nullptr) + continue; + if (port.second.net->driver.cell == nullptr || ctx->getBelGlobalBuf(port.second.net->driver.cell->bel)) + continue; + int &o = nbt_old[port.second.net->name]; + --o; + NPNR_ASSERT(o >= 0); + if (o > 0) + ++loss; + int &n = nbt_new[port.second.net->name]; + if (n > 0) + ++gain; + ++n; + } + int delta = gain - loss; + total_net_share += delta; + return delta; + } + + // Get the combined wirelen/timing metric + inline double curr_metric() + { + return lambda * curr_timing_cost + (1 - lambda) * curr_wirelen_cost - cfg.netShareWeight * total_net_share; + } + + // Map nets to their bounding box (so we can skip recompute for moves that do not exceed the bounds + std::vector net_bounds; + // Map net arcs to their timing cost (criticality * delay ns) + std::vector> net_arc_tcost; + + // Fast lookup for cell to clusters + dict> cluster2cell; + + // Wirelength and timing cost at last and current iteration + wirelen_t last_wirelen_cost, curr_wirelen_cost; + double last_timing_cost, curr_timing_cost; + + Context *ctx; + float temp = 10; + float crit_exp = 8; + float lambda = 0.5; + bool improved = false; + int n_move, n_accept; + int diameter = 35, max_x = 1, max_y = 1; + dict> bel_types; + dict region_bounds; + FastBels fast_bels; + pool locked_bels; + std::vector net_by_udata; + std::vector old_udata; + bool require_legal = true; + const int legalise_dia = 4; + Placer1Cfg cfg; + + TimingAnalyser tmg; +}; + +Placer1Cfg::Placer1Cfg(Context *ctx) +{ + constraintWeight = ctx->setting("placer1/constraintWeight", 10); + netShareWeight = ctx->setting("placer1/netShareWeight", 0); + minBelsForGridPick = ctx->setting("placer1/minBelsForGridPick", 64); + budgetBased = ctx->setting("placer1/budgetBased", false); + startTemp = ctx->setting("placer1/startTemp", 1); + timingFanoutThresh = std::numeric_limits::max(); + timing_driven = ctx->setting("timing_driven"); + slack_redist_iter = ctx->setting("slack_redist_iter"); + hpwl_scale_x = 1; + hpwl_scale_y = 1; +} + +bool placer1(Context *ctx, Placer1Cfg cfg) +{ + try { + SAPlacer placer(ctx, cfg); + placer.place(); + log_info("Checksum: 0x%08x\n", ctx->checksum()); +#ifndef NDEBUG + ctx->lock(); + ctx->check(); + ctx->unlock(); +#endif + return true; + } catch (log_execution_error_exception) { +#ifndef NDEBUG + ctx->lock(); + ctx->check(); + ctx->unlock(); +#endif + return false; + } +} + +bool placer1_refine(Context *ctx, Placer1Cfg cfg) +{ + try { + SAPlacer placer(ctx, cfg); + placer.place(true); + log_info("Checksum: 0x%08x\n", ctx->checksum()); +#ifndef NDEBUG + ctx->lock(); + ctx->check(); + ctx->unlock(); +#endif + return true; + } catch (log_execution_error_exception) { +#ifndef NDEBUG + ctx->lock(); + ctx->check(); + ctx->unlock(); +#endif + return false; + } +} + +NEXTPNR_NAMESPACE_END diff --git a/common/place/placer1.h b/common/place/placer1.h new file mode 100644 index 00000000..9dfb0b0d --- /dev/null +++ b/common/place/placer1.h @@ -0,0 +1,45 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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 PLACE_H +#define PLACE_H + +#include "log.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct Placer1Cfg +{ + Placer1Cfg(Context *ctx); + float constraintWeight, netShareWeight; + int minBelsForGridPick; + bool budgetBased; + float startTemp; + int timingFanoutThresh; + bool timing_driven; + int slack_redist_iter; + int hpwl_scale_x, hpwl_scale_y; +}; + +extern bool placer1(Context *ctx, Placer1Cfg cfg); +extern bool placer1_refine(Context *ctx, Placer1Cfg cfg); + +NEXTPNR_NAMESPACE_END + +#endif // PLACE_H diff --git a/common/place/placer_heap.cc b/common/place/placer_heap.cc new file mode 100644 index 00000000..4c9ffb23 --- /dev/null +++ b/common/place/placer_heap.cc @@ -0,0 +1,1830 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 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. + * + * [[cite]] HeAP + * Analytical Placement for Heterogeneous FPGAs, Marcel Gort and Jason H. Anderson + * https://janders.eecg.utoronto.ca/pdfs/marcelfpl12.pdf + * + * [[cite]] SimPL + * SimPL: An Effective Placement Algorithm, Myung-Chul Kim, Dong-Jin Lee and Igor L. Markov + * http://www.ece.umich.edu/cse/awards/pdfs/iccad10-simpl.pdf + * + * Notable changes from the original algorithm + * - Following the other nextpnr placer, Bels are placed rather than CLBs. This means a strict legalisation pass is + * added in addition to coarse legalisation (referred to as "spreading" to avoid confusion with strict legalisation) + * as described in HeAP to ensure validity. This searches random bels in the vicinity of the position chosen by + * spreading, with diameter increasing over iterations, with a heuristic to prefer lower wirelength choices. + * - To make the placer timing-driven, the bound2bound weights are multiplied by (1 + 10 * crit^2) + */ + +#ifdef WITH_HEAP + +#include "placer_heap.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fast_bels.h" +#include "log.h" +#include "nextpnr.h" +#include "parallel_refine.h" +#include "place_common.h" +#include "placer1.h" +#include "scope_lock.h" +#include "timing.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +// A simple internal representation for a sparse system of equations Ax = rhs +// This is designed to decouple the functions that build the matrix to the engine that +// solves it, and the representation that requires +template struct EquationSystem +{ + + EquationSystem(size_t rows, size_t cols) + { + A.resize(cols); + rhs.resize(rows); + } + + // Simple sparse format, easy to convert to CCS for solver + std::vector>> A; // col -> (row, x[row, col]) sorted by row + std::vector rhs; // RHS vector + void reset() + { + for (auto &col : A) + col.clear(); + std::fill(rhs.begin(), rhs.end(), T()); + } + + void add_coeff(int row, int col, T val) + { + auto &Ac = A.at(col); + // Binary search + int b = 0, e = int(Ac.size()) - 1; + while (b <= e) { + int i = (b + e) / 2; + if (Ac.at(i).first == row) { + Ac.at(i).second += val; + return; + } + if (Ac.at(i).first > row) + e = i - 1; + else + b = i + 1; + } + Ac.insert(Ac.begin() + b, std::make_pair(row, val)); + } + + void add_rhs(int row, T val) { rhs[row] += val; } + + void solve(std::vector &x, float tolerance) + { + using namespace Eigen; + if (x.empty()) + return; + NPNR_ASSERT(x.size() == A.size()); + + VectorXd vx(x.size()), vb(rhs.size()); + SparseMatrix mat(A.size(), A.size()); + + std::vector colnnz; + for (auto &Ac : A) + colnnz.push_back(int(Ac.size())); + mat.reserve(colnnz); + for (int col = 0; col < int(A.size()); col++) { + auto &Ac = A.at(col); + for (auto &el : Ac) + mat.insert(el.first, col) = el.second; + } + + for (int i = 0; i < int(x.size()); i++) + vx[i] = x.at(i); + for (int i = 0; i < int(rhs.size()); i++) + vb[i] = rhs.at(i); + + ConjugateGradient, Lower | Upper> solver; + solver.setTolerance(tolerance); + VectorXd xr = solver.compute(mat).solveWithGuess(vb, vx); + for (int i = 0; i < int(x.size()); i++) + x.at(i) = xr[i]; + // for (int i = 0; i < int(x.size()); i++) + // log_info("x[%d] = %f\n", i, x.at(i)); + } +}; + +} // namespace + +class HeAPPlacer +{ + public: + HeAPPlacer(Context *ctx, PlacerHeapCfg cfg) + : ctx(ctx), cfg(cfg), fast_bels(ctx, /*check_bel_available=*/true, -1), tmg(ctx) + { + Eigen::initParallel(); + tmg.setup_only = true; + tmg.setup(); + + for (auto &cell : ctx->cells) + if (cell.second->cluster != ClusterId()) + cluster2cells[cell.second->cluster].push_back(cell.second.get()); + } + + bool place() + { + auto startt = std::chrono::high_resolution_clock::now(); + + ScopeLock lock(ctx); + place_constraints(); + build_fast_bels(); + seed_placement(); + update_all_chains(); + wirelen_t hpwl = total_hpwl(); + log_info("Creating initial analytic placement for %d cells, random placement wirelen = %d.\n", + int(place_cells.size()), int(hpwl)); + for (int i = 0; i < 4; i++) { + setup_solve_cells(); + auto solve_startt = std::chrono::high_resolution_clock::now(); +#ifdef NPNR_DISABLE_THREADS + build_solve_direction(false, -1); + build_solve_direction(true, -1); +#else + boost::thread xaxis([&]() { build_solve_direction(false, -1); }); + build_solve_direction(true, -1); + xaxis.join(); +#endif + auto solve_endt = std::chrono::high_resolution_clock::now(); + solve_time += std::chrono::duration(solve_endt - solve_startt).count(); + + update_all_chains(); + + hpwl = total_hpwl(); + log_info(" at initial placer iter %d, wirelen = %d\n", i, int(hpwl)); + } + + wirelen_t solved_hpwl = 0, spread_hpwl = 0, legal_hpwl = 0, best_hpwl = std::numeric_limits::max(); + int iter = 0, stalled = 0; + + std::vector> solution; + + std::vector> heap_runs; + pool all_buckets; + dict bucket_count; + + for (auto cell : place_cells) { + BelBucketId bucket = ctx->getBelBucketForCellType(cell->type); + if (!all_buckets.count(bucket)) { + heap_runs.push_back(pool{bucket}); + all_buckets.insert(bucket); + } + bucket_count[bucket]++; + } + // If more than 98% of cells are one cell type, always solve all at once + // Otherwise, follow full HeAP strategy of rotate&all + for (auto &c : bucket_count) { + if (c.second >= 0.98 * int(place_cells.size())) { + heap_runs.clear(); + break; + } + } + + if (cfg.placeAllAtOnce) { + // Never want to deal with LUTs, FFs, MUXFxs separately, + // for now disable all single-cell-type runs and only have heterogeneous + // runs + heap_runs.clear(); + } + + heap_runs.push_back(all_buckets); + // The main HeAP placer loop + log_info("Running main analytical placer.\n"); + while (stalled < 5 && (solved_hpwl <= legal_hpwl * 0.8)) { + // Alternate between particular bel types and all bels + for (auto &run : heap_runs) { + auto run_startt = std::chrono::high_resolution_clock::now(); + + setup_solve_cells(&run); + if (solve_cells.empty()) + continue; + // Heuristic: don't bother with threading below a certain size + auto solve_startt = std::chrono::high_resolution_clock::now(); + + // Build the connectivity matrix and run the solver; multithreaded between x and y axes if applicable +#ifndef NPNR_DISABLE_THREADS + if (solve_cells.size() >= 500) { + boost::thread xaxis([&]() { build_solve_direction(false, (iter == 0) ? -1 : iter); }); + build_solve_direction(true, (iter == 0) ? -1 : iter); + xaxis.join(); + } else +#endif + { + build_solve_direction(false, (iter == 0) ? -1 : iter); + build_solve_direction(true, (iter == 0) ? -1 : iter); + } + auto solve_endt = std::chrono::high_resolution_clock::now(); + solve_time += std::chrono::duration(solve_endt - solve_startt).count(); + update_all_chains(); + solved_hpwl = total_hpwl(); + + update_all_chains(); + + // Run the spreader + for (const auto &group : cfg.cellGroups) + CutSpreader(this, group).run(); + + for (auto type : run) + if (std::all_of(cfg.cellGroups.begin(), cfg.cellGroups.end(), + [type](const pool &grp) { return !grp.count(type); })) + CutSpreader(this, {type}).run(); + + // Run strict legalisation to find a valid bel for all cells + update_all_chains(); + spread_hpwl = total_hpwl(); + legalise_placement_strict(true); + update_all_chains(); + + legal_hpwl = total_hpwl(); + auto run_stopt = std::chrono::high_resolution_clock::now(); + + IdString bucket_name = ctx->getBelBucketName(*run.begin()); + log_info(" at iteration #%d, type %s: wirelen solved = %d, spread = %d, legal = %d; time = %.02fs\n", + iter + 1, (run.size() > 1 ? "ALL" : bucket_name.c_str(ctx)), int(solved_hpwl), + int(spread_hpwl), int(legal_hpwl), + std::chrono::duration(run_stopt - run_startt).count()); + } + + // Update timing weights + if (cfg.timing_driven) + tmg.run(); + + if (legal_hpwl < best_hpwl) { + best_hpwl = legal_hpwl; + stalled = 0; + // Save solution + solution.clear(); + for (auto &cell : ctx->cells) { + solution.emplace_back(cell.second.get(), cell.second->bel, cell.second->belStrength); + } + } else { + ++stalled; + } + for (auto &cl : cell_locs) { + cl.second.legal_x = cl.second.x; + cl.second.legal_y = cl.second.y; + } + ctx->yield(); + ++iter; + } + + // Apply saved solution + for (auto &sc : solution) { + CellInfo *cell = std::get<0>(sc); + if (cell->bel != BelId()) + ctx->unbindBel(cell->bel); + } + for (auto &sc : solution) { + CellInfo *cell; + BelId bel; + PlaceStrength strength; + std::tie(cell, bel, strength) = sc; + ctx->bindBel(bel, cell, strength); + } + + for (auto &cell : ctx->cells) { + if (cell.second->bel == BelId()) + log_error("Found unbound cell %s\n", cell.first.c_str(ctx)); + if (ctx->getBoundBelCell(cell.second->bel) != cell.second.get()) + log_error("Found cell %s with mismatched binding\n", cell.first.c_str(ctx)); + if (ctx->debug) + log_info("AP soln: %s -> %s\n", cell.first.c_str(ctx), ctx->nameOfBel(cell.second->bel)); + } + + bool any_bad_placements = false; + for (auto bel : ctx->getBels()) { + CellInfo *cell = ctx->getBoundBelCell(bel); + if (!ctx->isBelLocationValid(bel)) { + std::string cell_text = "no cell"; + if (cell != nullptr) + cell_text = std::string("cell '") + ctx->nameOf(cell) + "'"; + log_warning("post-placement validity check failed for Bel '%s' " + "(%s)\n", + ctx->nameOfBel(bel), cell_text.c_str()); + any_bad_placements = true; + } + } + + if (any_bad_placements) { + return false; + } + + auto endtt = std::chrono::high_resolution_clock::now(); + log_info("HeAP Placer Time: %.02fs\n", std::chrono::duration(endtt - startt).count()); + log_info(" of which solving equations: %.02fs\n", solve_time); + log_info(" of which spreading cells: %.02fs\n", cl_time); + log_info(" of which strict legalisation: %.02fs\n", sl_time); + + ctx->check(); + lock.unlock_early(); + +#if !defined(__wasm) + if (cfg.parallelRefine) { + if (!parallel_refine(ctx, ParallelRefineCfg(ctx))) { + return false; + } + } else +#endif + { + if (!placer1_refine(ctx, Placer1Cfg(ctx))) { + return false; + } + } + + return true; + } + + private: + Context *ctx; + PlacerHeapCfg cfg; + + int max_x = 0, max_y = 0; + FastBels fast_bels; + dict> bel_types; + + TimingAnalyser tmg; + + struct BoundingBox + { + // Actual bounding box + int x0 = 0, x1 = 0, y0 = 0, y1 = 0; + }; + + dict constraint_region_bounds; + + // In some cases, we can't use bindBel because we allow overlap in the earlier stages. So we use this custom + // structure instead + struct CellLocation + { + int x, y; + int legal_x, legal_y; + double rawx, rawy; + bool locked, global; + }; + dict cell_locs; + // The set of cells that we will actually place. This excludes locked cells and children cells of macros/chains + // (only the root of each macro is placed.) + std::vector place_cells; + + // The cells in the current equation being solved (a subset of place_cells in some cases, where we only place + // cells of a certain type) + std::vector solve_cells; + + dict> cluster2cells; + dict chain_size; + // Performance counting + double solve_time = 0, cl_time = 0, sl_time = 0; + + // Place cells with the BEL attribute set to constrain them + void place_constraints() + { + size_t placed_cells = 0; + // Initial constraints placer + for (auto &cell_entry : ctx->cells) { + CellInfo *cell = cell_entry.second.get(); + + auto loc = cell->attrs.find(ctx->id("BEL")); + if (loc != cell->attrs.end()) { + std::string loc_name = loc->second.as_string(); + BelId bel = ctx->getBelByNameStr(loc_name); + if (bel == BelId()) { + log_error("No Bel named \'%s\' located for " + "this chip (processing BEL attribute on \'%s\')\n", + loc_name.c_str(), cell->name.c_str(ctx)); + } + + if (!ctx->isValidBelForCellType(cell->type, bel)) { + IdString bel_type = ctx->getBelType(bel); + log_error("Bel \'%s\' of type \'%s\' does not match cell " + "\'%s\' of type \'%s\'\n", + loc_name.c_str(), bel_type.c_str(ctx), cell->name.c_str(ctx), cell->type.c_str(ctx)); + } + auto bound_cell = ctx->getBoundBelCell(bel); + if (bound_cell) { + log_error("Cell \'%s\' cannot be bound to bel \'%s\' since it is already bound to cell \'%s\'\n", + cell->name.c_str(ctx), loc_name.c_str(), bound_cell->name.c_str(ctx)); + } + + ctx->bindBel(bel, cell, STRENGTH_USER); + if (!ctx->isBelLocationValid(bel)) { + IdString bel_type = ctx->getBelType(bel); + log_error("Bel \'%s\' of type \'%s\' is not valid for cell " + "\'%s\' of type \'%s\'\n", + loc_name.c_str(), bel_type.c_str(ctx), cell->name.c_str(ctx), cell->type.c_str(ctx)); + } + placed_cells++; + } + } + log_info("Placed %d cells based on constraints.\n", int(placed_cells)); + ctx->yield(); + } + + void build_fast_bels() + { + for (auto bel : ctx->getBels()) { + if (!ctx->checkBelAvail(bel)) + continue; + Loc loc = ctx->getBelLocation(bel); + max_x = std::max(max_x, loc.x); + max_y = std::max(max_y, loc.y); + } + + pool cell_types_in_use; + pool buckets_in_use; + for (auto &cell : ctx->cells) { + IdString cell_type = cell.second->type; + cell_types_in_use.insert(cell_type); + BelBucketId bucket = ctx->getBelBucketForCellType(cell_type); + buckets_in_use.insert(bucket); + } + + for (auto cell_type : cell_types_in_use) { + fast_bels.addCellType(cell_type); + } + for (auto bucket : buckets_in_use) { + fast_bels.addBelBucket(bucket); + } + + // Determine bounding boxes of region constraints + for (auto ®ion : ctx->region) { + Region *r = region.second.get(); + BoundingBox bb; + if (r->constr_bels) { + bb.x0 = std::numeric_limits::max(); + bb.x1 = std::numeric_limits::min(); + bb.y0 = std::numeric_limits::max(); + bb.y1 = std::numeric_limits::min(); + for (auto bel : r->bels) { + Loc loc = ctx->getBelLocation(bel); + bb.x0 = std::min(bb.x0, loc.x); + bb.x1 = std::max(bb.x1, loc.x); + bb.y0 = std::min(bb.y0, loc.y); + bb.y1 = std::max(bb.y1, loc.y); + } + } else { + bb.x0 = 0; + bb.y0 = 0; + bb.x1 = max_x; + bb.y1 = max_y; + } + constraint_region_bounds[r->name] = bb; + } + } + + // Build and solve in one direction + void build_solve_direction(bool yaxis, int iter) + { + for (int i = 0; i < 5; i++) { + EquationSystem esx(solve_cells.size(), solve_cells.size()); + build_equations(esx, yaxis, iter); + solve_equations(esx, yaxis); + } + } + + // Check if a cell has any meaningful connectivity + bool has_connectivity(CellInfo *cell) + { + for (auto port : cell->ports) { + if (port.second.net != nullptr && port.second.net->driver.cell != nullptr && + !port.second.net->users.empty()) + return true; + } + return false; + } + + // Build up a random initial placement, without regard to legality + // FIXME: Are there better approaches to the initial placement (e.g. greedy?) + void seed_placement() + { + pool cell_types; + for (const auto &cell : ctx->cells) { + cell_types.insert(cell.second->type); + } + + pool bels_used; + dict> available_bels; + + for (auto bel : ctx->getBels()) { + if (!ctx->checkBelAvail(bel)) { + continue; + } + + for (auto cell_type : cell_types) { + if (ctx->isValidBelForCellType(cell_type, bel)) { + available_bels[cell_type].push_back(bel); + } + } + } + + for (auto &t : available_bels) { + ctx->shuffle(t.second.begin(), t.second.end()); + } + + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->bel != BelId()) { + Loc loc = ctx->getBelLocation(ci->bel); + cell_locs[cell.first].x = loc.x; + cell_locs[cell.first].y = loc.y; + cell_locs[cell.first].locked = true; + cell_locs[cell.first].global = ctx->getBelGlobalBuf(ci->bel); + } else if (ci->cluster == ClusterId() || ctx->getClusterRootCell(ci->cluster) == ci) { + bool placed = false; + int attempt_count = 0; + while (!placed) { + ++attempt_count; + if (attempt_count > 25000) { + log_error("Unable to find a placement location for cell '%s'\n", ci->name.c_str(ctx)); + } + + // Make sure this cell type is in the available BEL map at + // all. + if (!available_bels.count(ci->type)) { + log_error("Unable to place cell '%s', no BELs remaining to implement cell type '%s'\n", + ci->name.c_str(ctx), ci->type.c_str(ctx)); + } + + // Find an unused BEL from bels_for_cell_type. + auto &bels_for_cell_type = available_bels.at(ci->type); + BelId bel; + while (true) { + if (bels_for_cell_type.empty()) { + log_error("Unable to place cell '%s', no BELs remaining to implement cell type '%s'\n", + ci->name.c_str(ctx), ci->type.c_str(ctx)); + } + + BelId candidate_bel = bels_for_cell_type.back(); + bels_for_cell_type.pop_back(); + if (bels_used.count(candidate_bel)) { + // candidate_bel has already been used by another + // cell type, skip it. + continue; + } + + bel = candidate_bel; + break; + } + + Loc loc = ctx->getBelLocation(bel); + cell_locs[cell.first].x = loc.x; + cell_locs[cell.first].y = loc.y; + cell_locs[cell.first].locked = false; + cell_locs[cell.first].global = ctx->getBelGlobalBuf(bel); + + // FIXME + if (has_connectivity(cell.second.get()) && !cfg.ioBufTypes.count(ci->type)) { + bels_used.insert(bel); + place_cells.push_back(ci); + placed = true; + } else { + ctx->bindBel(bel, ci, STRENGTH_STRONG); + if (ctx->isBelLocationValid(bel)) { + cell_locs[cell.first].locked = true; + placed = true; + bels_used.insert(bel); + } else { + ctx->unbindBel(bel); + available_bels.at(ci->type).push_front(bel); + } + } + } + } + } + } + + // Setup the cells to be solved, returns the number of rows + int setup_solve_cells(pool *buckets = nullptr) + { + int row = 0; + solve_cells.clear(); + // First clear the udata of all cells + for (auto &cell : ctx->cells) + cell.second->udata = dont_solve; + // Then update cells to be placed, which excludes cell children + for (auto cell : place_cells) { + if (buckets && !buckets->count(ctx->getBelBucketForCellType(cell->type))) + continue; + cell->udata = row++; + solve_cells.push_back(cell); + } + // Finally, update the udata of children + for (auto &cluster : cluster2cells) + for (auto child : cluster.second) + child->udata = ctx->getClusterRootCell(cluster.first)->udata; + return row; + } + + // Update all chains + void update_all_chains() + { + for (auto cell : place_cells) { + chain_size[cell->name] = 1; + if (cell->cluster != ClusterId()) { + const auto base = cell_locs[cell->name]; + for (auto child : cluster2cells.at(cell->cluster)) { + if (child->type == cell->type && child != cell) + chain_size[cell->name]++; + Loc offset = ctx->getClusterOffset(child); + cell_locs[child->name].x = std::max(0, std::min(max_x, base.x + offset.x)); + cell_locs[child->name].y = std::max(0, std::min(max_y, base.y + offset.y)); + } + } + } + } + + // Run a function on all ports of a net - including the driver and all users + template void foreach_port(NetInfo *net, Tf func) + { + if (net->driver.cell != nullptr) + func(net->driver, store_index()); + for (auto usr : net->users.enumerate()) + func(usr.value, usr.index); + } + + // Build the system of equations for either X or Y + void build_equations(EquationSystem &es, bool yaxis, int iter = -1) + { + // Return the x or y position of a cell, depending on ydir + auto cell_pos = [&](CellInfo *cell) { return yaxis ? cell_locs.at(cell->name).y : cell_locs.at(cell->name).x; }; + auto legal_pos = [&](CellInfo *cell) { + return yaxis ? cell_locs.at(cell->name).legal_y : cell_locs.at(cell->name).legal_x; + }; + + es.reset(); + + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (ni->driver.cell == nullptr) + continue; + if (ni->users.empty()) + continue; + if (cell_locs.at(ni->driver.cell->name).global) + continue; + // Find the bounds of the net in this axis, and the ports that correspond to these bounds + PortRef *lbport = nullptr, *ubport = nullptr; + int lbpos = std::numeric_limits::max(), ubpos = std::numeric_limits::min(); + foreach_port(ni, [&](PortRef &port, store_index user_idx) { + int pos = cell_pos(port.cell); + if (pos < lbpos) { + lbpos = pos; + lbport = &port; + } + if (pos > ubpos) { + ubpos = pos; + ubport = &port; + } + }); + NPNR_ASSERT(lbport != nullptr); + NPNR_ASSERT(ubport != nullptr); + + auto stamp_equation = [&](PortRef &var, PortRef &eqn, double weight) { + if (eqn.cell->udata == dont_solve) + return; + int row = eqn.cell->udata; + int v_pos = cell_pos(var.cell); + if (var.cell->udata != dont_solve) { + es.add_coeff(row, var.cell->udata, weight); + } else { + es.add_rhs(row, -v_pos * weight); + } + if (var.cell->cluster != ClusterId()) { + Loc offset = ctx->getClusterOffset(var.cell); + es.add_rhs(row, -(yaxis ? offset.y : offset.x) * weight); + } + }; + + // Add all relevant connections to the matrix + foreach_port(ni, [&](PortRef &port, store_index user_idx) { + int this_pos = cell_pos(port.cell); + auto process_arc = [&](PortRef *other) { + if (other == &port) + return; + int o_pos = cell_pos(other->cell); + double weight = 1.0 / (ni->users.entries() * + std::max(1, (yaxis ? cfg.hpwl_scale_y : cfg.hpwl_scale_x) * + std::abs(o_pos - this_pos))); + + if (user_idx) { + weight *= (1.0 + cfg.timingWeight * std::pow(tmg.get_criticality(CellPortKey(port)), + cfg.criticalityExponent)); + } + + // If cell 0 is not fixed, it will stamp +w on its equation and -w on the other end's equation, + // if the other end isn't fixed + stamp_equation(port, port, weight); + stamp_equation(port, *other, -weight); + stamp_equation(*other, *other, weight); + stamp_equation(*other, port, -weight); + }; + process_arc(lbport); + process_arc(ubport); + }); + } + if (iter != -1) { + float alpha = cfg.alpha; + for (size_t row = 0; row < solve_cells.size(); row++) { + int l_pos = legal_pos(solve_cells.at(row)); + int c_pos = cell_pos(solve_cells.at(row)); + + double weight = + alpha * iter / + std::max(1, (yaxis ? cfg.hpwl_scale_y : cfg.hpwl_scale_x) * std::abs(l_pos - c_pos)); + // Add an arc from legalised to current position + es.add_coeff(row, row, weight); + es.add_rhs(row, weight * l_pos); + } + } + } + + // Build the system of equations for either X or Y + void solve_equations(EquationSystem &es, bool yaxis) + { + // Return the x or y position of a cell, depending on ydir + auto cell_pos = [&](CellInfo *cell) { return yaxis ? cell_locs.at(cell->name).y : cell_locs.at(cell->name).x; }; + std::vector vals; + std::transform(solve_cells.begin(), solve_cells.end(), std::back_inserter(vals), cell_pos); + es.solve(vals, cfg.solverTolerance); + for (size_t i = 0; i < vals.size(); i++) + if (yaxis) { + cell_locs.at(solve_cells.at(i)->name).rawy = vals.at(i); + cell_locs.at(solve_cells.at(i)->name).y = std::min(max_y, std::max(0, int(vals.at(i)))); + if (solve_cells.at(i)->region != nullptr) + cell_locs.at(solve_cells.at(i)->name).y = + limit_to_reg(solve_cells.at(i)->region, cell_locs.at(solve_cells.at(i)->name).y, true); + } else { + cell_locs.at(solve_cells.at(i)->name).rawx = vals.at(i); + cell_locs.at(solve_cells.at(i)->name).x = std::min(max_x, std::max(0, int(vals.at(i)))); + if (solve_cells.at(i)->region != nullptr) + cell_locs.at(solve_cells.at(i)->name).x = + limit_to_reg(solve_cells.at(i)->region, cell_locs.at(solve_cells.at(i)->name).x, false); + } + } + + // Compute HPWL + wirelen_t total_hpwl() + { + wirelen_t hpwl = 0; + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (ni->driver.cell == nullptr) + continue; + CellLocation &drvloc = cell_locs.at(ni->driver.cell->name); + if (drvloc.global) + continue; + int xmin = drvloc.x, xmax = drvloc.x, ymin = drvloc.y, ymax = drvloc.y; + for (auto &user : ni->users) { + CellLocation &usrloc = cell_locs.at(user.cell->name); + xmin = std::min(xmin, usrloc.x); + xmax = std::max(xmax, usrloc.x); + ymin = std::min(ymin, usrloc.y); + ymax = std::max(ymax, usrloc.y); + } + hpwl += cfg.hpwl_scale_x * (xmax - xmin) + cfg.hpwl_scale_y * (ymax - ymin); + } + return hpwl; + } + + // Strict placement legalisation, performed after the initial HeAP spreading + void legalise_placement_strict(bool require_validity = false) + { + auto startt = std::chrono::high_resolution_clock::now(); + + // Unbind all cells placed in this solution + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->bel != BelId() && + (ci->udata != dont_solve || + (ci->cluster != ClusterId() && ctx->getClusterRootCell(ci->cluster)->udata != dont_solve))) + ctx->unbindBel(ci->bel); + } + + // At the moment we don't follow the full HeAP algorithm using cuts for legalisation, instead using + // the simple greedy largest-macro-first approach. + std::priority_queue> remaining; + for (auto cell : solve_cells) { + remaining.emplace(chain_size[cell->name], cell->name); + } + int ripup_radius = 2; + int total_iters = 0; + int total_iters_noreset = 0; + while (!remaining.empty()) { + auto top = remaining.top(); + remaining.pop(); + + CellInfo *ci = ctx->cells.at(top.second).get(); + // Was now placed, ignore + if (ci->bel != BelId()) + continue; + // log_info(" Legalising %s (%s)\n", top.second.c_str(ctx), ci->type.c_str(ctx)); + FastBels::FastBelsData *fb; + fast_bels.getBelsForCellType(ci->type, &fb); + int radius = 0; + int iter = 0; + int iter_at_radius = 0; + bool placed = false; + BelId bestBel; + int best_inp_len = std::numeric_limits::max(); + + total_iters++; + total_iters_noreset++; + if (total_iters > int(solve_cells.size())) { + total_iters = 0; + ripup_radius = std::max(std::max(max_x, max_y), ripup_radius * 2); + } + + if (total_iters_noreset > std::max(5000, 8 * int(ctx->cells.size()))) { + log_error("Unable to find legal placement for all cells, design is probably at utilisation limit.\n"); + } + + while (!placed) { + + // Set a conservative timeout + if (iter > std::max(10000, 3 * int(ctx->cells.size()))) + log_error("Unable to find legal placement for cell '%s', check constraints and utilisation.\n", + ctx->nameOf(ci)); + + // Determine a search radius around the solver location (which increases over time) that is clamped to + // the region constraint for the cell (if applicable) + int rx = radius, ry = radius; + + if (ci->region != nullptr) { + rx = std::min(radius, (constraint_region_bounds[ci->region->name].x1 - + constraint_region_bounds[ci->region->name].x0) / + 2 + + 1); + ry = std::min(radius, (constraint_region_bounds[ci->region->name].y1 - + constraint_region_bounds[ci->region->name].y0) / + 2 + + 1); + } + + // Pick a random X and Y location within our search radius + int nx = ctx->rng(2 * rx + 1) + std::max(cell_locs.at(ci->name).x - rx, 0); + int ny = ctx->rng(2 * ry + 1) + std::max(cell_locs.at(ci->name).y - ry, 0); + + iter++; + iter_at_radius++; + if (iter >= (10 * (radius + 1))) { + // No luck yet, increase radius + radius = std::min(std::max(max_x, max_y), radius + 1); + while (radius < std::max(max_x, max_y)) { + // Keep increasing the radius until it will actually increase the number of cells we are + // checking (e.g. BRAM and DSP will not be in all cols/rows), so we don't waste effort + for (int x = std::max(0, cell_locs.at(ci->name).x - radius); + x <= std::min(max_x, cell_locs.at(ci->name).x + radius); x++) { + if (x >= int(fb->size())) + break; + for (int y = std::max(0, cell_locs.at(ci->name).y - radius); + y <= std::min(max_y, cell_locs.at(ci->name).y + radius); y++) { + if (y >= int(fb->at(x).size())) + break; + if (fb->at(x).at(y).size() > 0) + goto notempty; + } + } + radius = std::min(std::max(max_x, max_y), radius + 1); + } + notempty: + iter_at_radius = 0; + iter = 0; + } + // If our randomly chosen cooridnate is out of bounds; or points to a tile with no relevant bels; ignore + // it + if (nx < 0 || nx > max_x) + continue; + if (ny < 0 || ny > max_y) + continue; + + if (nx >= int(fb->size())) + continue; + if (ny >= int(fb->at(nx).size())) + continue; + if (fb->at(nx).at(ny).empty()) + continue; + + // The number of attempts to find a location to try + int need_to_explore = 2 * radius; + + // If we have found at least one legal location; and made enough attempts; assume it's good enough and + // finish + if (iter_at_radius >= need_to_explore && bestBel != BelId()) { + CellInfo *bound = ctx->getBoundBelCell(bestBel); + if (bound != nullptr) { + ctx->unbindBel(bound->bel); + remaining.emplace(chain_size[bound->name], bound->name); + } + ctx->bindBel(bestBel, ci, STRENGTH_WEAK); + placed = true; + Loc loc = ctx->getBelLocation(bestBel); + cell_locs[ci->name].x = loc.x; + cell_locs[ci->name].y = loc.y; + break; + } + + if (ci->cluster == ClusterId()) { + // The case where we have no relative constraints + for (auto sz : fb->at(nx).at(ny)) { + // Look through all bels in this tile; checking region constraint if applicable + if (!ci->testRegion(sz)) + continue; + // Prefer available bels; unless we are dealing with a wide radius (e.g. difficult control sets) + // or occasionally trigger a tiebreaker + if (ctx->checkBelAvail(sz) || (radius > ripup_radius || ctx->rng(20000) < 10)) { + CellInfo *bound = ctx->getBoundBelCell(sz); + if (bound != nullptr) { + // Only rip up cells without constraints + if (bound->cluster != ClusterId()) + continue; + ctx->unbindBel(bound->bel); + } + // Provisionally bind the bel + ctx->bindBel(sz, ci, STRENGTH_WEAK); + if (require_validity && !ctx->isBelLocationValid(sz)) { + // New location is not legal; unbind the cell (and rebind the cell we ripped up if + // applicable) + ctx->unbindBel(sz); + if (bound != nullptr) + ctx->bindBel(sz, bound, STRENGTH_WEAK); + } else if (iter_at_radius < need_to_explore) { + // It's legal, but we haven't tried enough locations yet + ctx->unbindBel(sz); + if (bound != nullptr) + ctx->bindBel(sz, bound, STRENGTH_WEAK); + int input_len = 0; + // Compute a fast input wirelength metric at this bel; and save if better than our last + // try + for (auto &port : ci->ports) { + auto &p = port.second; + if (p.type != PORT_IN || p.net == nullptr || p.net->driver.cell == nullptr) + continue; + CellInfo *drv = p.net->driver.cell; + auto drv_loc = cell_locs.find(drv->name); + if (drv_loc == cell_locs.end()) + continue; + if (drv_loc->second.global) + continue; + input_len += std::abs(drv_loc->second.x - nx) + std::abs(drv_loc->second.y - ny); + } + if (input_len < best_inp_len) { + best_inp_len = input_len; + bestBel = sz; + } + break; + } else { + // It's legal, and we've tried enough. Finish. + if (bound != nullptr) + remaining.emplace(chain_size[bound->name], bound->name); + Loc loc = ctx->getBelLocation(sz); + cell_locs[ci->name].x = loc.x; + cell_locs[ci->name].y = loc.y; + placed = true; + break; + } + } + } + } else { + // We do have relative constraints + for (auto sz : fb->at(nx).at(ny)) { + // List of cells and their destination + std::vector> targets; + // List of bels we placed things at; and the cell that was there before if applicable + std::vector> swaps_made; + + if (!ctx->getClusterPlacement(ci->cluster, sz, targets)) + continue; + + for (auto &target : targets) { + // Check it satisfies the region constraint if applicable + if (!target.first->testRegion(target.second)) + goto fail; + CellInfo *bound = ctx->getBoundBelCell(target.second); + // Chains cannot overlap; so if we have to ripup a cell make sure it isn't part of a chain + if (bound != nullptr) + if (bound->cluster != ClusterId() || bound->belStrength > STRENGTH_WEAK) + goto fail; + } + // Actually perform the move; keeping track of the moves we make so we can revert them if needed + for (auto &target : targets) { + CellInfo *bound = ctx->getBoundBelCell(target.second); + if (bound != nullptr) + ctx->unbindBel(target.second); + ctx->bindBel(target.second, target.first, STRENGTH_STRONG); + swaps_made.emplace_back(target.second, bound); + } + // Check that the move we have made is legal + for (auto &sm : swaps_made) { + if (!ctx->isBelLocationValid(sm.first)) + goto fail; + } + + if (false) { + fail: + // If the move turned out to be illegal; revert all the moves we made + for (auto &swap : swaps_made) { + ctx->unbindBel(swap.first); + if (swap.second != nullptr) + ctx->bindBel(swap.first, swap.second, STRENGTH_WEAK); + } + continue; + } + for (auto &target : targets) { + Loc loc = ctx->getBelLocation(target.second); + cell_locs[target.first->name].x = loc.x; + cell_locs[target.first->name].y = loc.y; + // log_info("%s %d %d %d\n", target.first->name.c_str(ctx), loc.x, loc.y, loc.z); + } + for (auto &swap : swaps_made) { + // Where we have ripped up cells; add them to the queue + if (swap.second != nullptr) + remaining.emplace(chain_size[swap.second->name], swap.second->name); + } + + placed = true; + break; + } + } + } + } + auto endt = std::chrono::high_resolution_clock::now(); + sl_time += std::chrono::duration(endt - startt).count(); + } + // Implementation of the cut-based spreading as described in the HeAP/SimPL papers + + template T limit_to_reg(Region *reg, T val, bool dir) + { + if (reg == nullptr) + return val; + int limit_low = dir ? constraint_region_bounds[reg->name].y0 : constraint_region_bounds[reg->name].x0; + int limit_high = dir ? constraint_region_bounds[reg->name].y1 : constraint_region_bounds[reg->name].x1; + return std::max(std::min(val, limit_high), limit_low); + } + + struct ChainExtent + { + int x0, y0, x1, y1; + }; + + struct SpreaderRegion + { + int id; + int x0, y0, x1, y1; + std::vector cells, bels; + bool overused(float beta) const + { + for (size_t t = 0; t < cells.size(); t++) { + if (bels.at(t) < 4) { + if (cells.at(t) > bels.at(t)) + return true; + } else { + if (cells.at(t) > beta * bels.at(t)) + return true; + } + } + return false; + } + }; + + class CutSpreader + { + public: + CutSpreader(HeAPPlacer *p, const pool &buckets) : p(p), ctx(p->ctx), buckets(buckets) + { + // Get fast BELs data for all buckets being Cut/Spread. + size_t idx = 0; + for (BelBucketId bucket : buckets) { + type_index[bucket] = idx; + FastBels::FastBelsData *fast_bels; + p->fast_bels.getBelsForBelBucket(bucket, &fast_bels); + fb.push_back(fast_bels); + ++idx; + NPNR_ASSERT(fb.size() == idx); + } + } + static int seq; + void run() + { + auto startt = std::chrono::high_resolution_clock::now(); + init(); + find_overused_regions(); + for (auto &r : regions) { + if (merged_regions.count(r.id)) + continue; +#if 0 + log_info("%s (%d, %d) |_> (%d, %d) %d/%d\n", beltype.c_str(ctx), r.x0, r.y0, r.x1, r.y1, r.cells, + r.bels); +#endif + } + expand_regions(); + std::queue> workqueue; +#if 0 + std::vector> orig; + if (ctx->debug) + for (auto c : p->solve_cells) + orig.emplace_back(p->cell_locs[c->name].rawx, p->cell_locs[c->name].rawy); +#endif + for (auto &r : regions) { + if (merged_regions.count(r.id)) + continue; +#if 0 + for (auto t : sorted(beltype)) { + log_info("%s (%d, %d) |_> (%d, %d) %d/%d\n", t.c_str(ctx), r.x0, r.y0, r.x1, r.y1, + r.cells.at(type_index.at(t)), r.bels.at(type_index.at(t))); + } + +#endif + workqueue.emplace(r.id, false); + } + while (!workqueue.empty()) { + auto front = workqueue.front(); + workqueue.pop(); + auto &r = regions.at(front.first); + if (std::all_of(r.cells.begin(), r.cells.end(), [](int x) { return x == 0; })) + continue; + auto res = cut_region(r, front.second); + if (res) { + workqueue.emplace(res->first, !front.second); + workqueue.emplace(res->second, !front.second); + } else { + // Try the other dir, in case stuck in one direction only + auto res2 = cut_region(r, !front.second); + if (res2) { + workqueue.emplace(res2->first, front.second); + workqueue.emplace(res2->second, front.second); + } + } + } +#if 0 + if (ctx->debug) { + std::ofstream sp("spread" + std::to_string(seq) + ".csv"); + for (size_t i = 0; i < p->solve_cells.size(); i++) { + auto &c = p->solve_cells.at(i); + if (c->type != beltype) + continue; + sp << orig.at(i).first << "," << orig.at(i).second << "," << p->cell_locs[c->name].rawx << "," << p->cell_locs[c->name].rawy << std::endl; + } + std::ofstream oc("cells" + std::to_string(seq) + ".csv"); + for (size_t y = 0; y <= p->max_y; y++) { + for (size_t x = 0; x <= p->max_x; x++) { + oc << cells_at_location.at(x).at(y).size() << ", "; + } + oc << std::endl; + } + ++seq; + } +#endif + auto endt = std::chrono::high_resolution_clock::now(); + p->cl_time += std::chrono::duration(endt - startt).count(); + } + + private: + HeAPPlacer *p; + Context *ctx; + pool buckets; + dict type_index; + std::vector>> occupancy; + std::vector> groups; + std::vector> chaines; + std::map cell_extents; + + std::vector>> *> fb; + + std::vector regions; + pool merged_regions; + // Cells at a location, sorted by real (not integer) x and y + std::vector>> cells_at_location; + + int occ_at(int x, int y, int type) { return occupancy.at(x).at(y).at(type); } + + int bels_at(int x, int y, int type) + { + if (x >= int(fb.at(type)->size()) || y >= int(fb.at(type)->at(x).size())) + return 0; + return int(fb.at(type)->at(x).at(y).size()); + } + + bool is_cell_fixed(const CellInfo &cell) const + { + return buckets.count(ctx->getBelBucketForCellType(cell.type)) == 0; + } + + size_t cell_index(const CellInfo &cell) const { return type_index.at(ctx->getBelBucketForCellType(cell.type)); } + + void init() + { + occupancy.resize(p->max_x + 1, + std::vector>(p->max_y + 1, std::vector(buckets.size(), 0))); + groups.resize(p->max_x + 1, std::vector(p->max_y + 1, -1)); + chaines.resize(p->max_x + 1, std::vector(p->max_y + 1)); + cells_at_location.resize(p->max_x + 1, std::vector>(p->max_y + 1)); + for (int x = 0; x <= p->max_x; x++) + for (int y = 0; y <= p->max_y; y++) { + for (int t = 0; t < int(buckets.size()); t++) { + occupancy.at(x).at(y).at(t) = 0; + } + groups.at(x).at(y) = -1; + chaines.at(x).at(y) = {x, y, x, y}; + } + + auto set_chain_ext = [&](IdString cell, int x, int y) { + if (!cell_extents.count(cell)) + cell_extents[cell] = {x, y, x, y}; + else { + cell_extents[cell].x0 = std::min(cell_extents[cell].x0, x); + cell_extents[cell].y0 = std::min(cell_extents[cell].y0, y); + cell_extents[cell].x1 = std::max(cell_extents[cell].x1, x); + cell_extents[cell].y1 = std::max(cell_extents[cell].y1, y); + } + }; + + for (auto &cell_loc : p->cell_locs) { + IdString cell_name = cell_loc.first; + const CellInfo &cell = *ctx->cells.at(cell_name); + const CellLocation &loc = cell_loc.second; + if (is_cell_fixed(cell)) { + continue; + } + + if (cell.belStrength > STRENGTH_STRONG) { + continue; + } + + occupancy.at(cell_loc.second.x).at(cell_loc.second.y).at(cell_index(cell))++; + + // Compute ultimate extent of each chain root + if (cell.cluster != ClusterId()) { + set_chain_ext(ctx->getClusterRootCell(cell.cluster)->name, loc.x, loc.y); + } + } + + for (auto &cell_loc : p->cell_locs) { + IdString cell_name = cell_loc.first; + const CellInfo &cell = *ctx->cells.at(cell_name); + const CellLocation &loc = cell_loc.second; + if (is_cell_fixed(cell)) { + continue; + } + + if (cell.belStrength > STRENGTH_STRONG) { + continue; + } + + // Transfer chain extents to the actual chains structure + ChainExtent *ce = nullptr; + if (cell.cluster != ClusterId()) { + ce = &(cell_extents.at(ctx->getClusterRootCell(cell.cluster)->name)); + } + + if (ce) { + auto &lce = chaines.at(loc.x).at(loc.y); + lce.x0 = std::min(lce.x0, ce->x0); + lce.y0 = std::min(lce.y0, ce->y0); + lce.x1 = std::max(lce.x1, ce->x1); + lce.y1 = std::max(lce.y1, ce->y1); + } + } + + for (auto cell : p->solve_cells) { + if (is_cell_fixed(*cell)) { + continue; + } + + cells_at_location.at(p->cell_locs.at(cell->name).x).at(p->cell_locs.at(cell->name).y).push_back(cell); + } + } + + void merge_regions(SpreaderRegion &merged, SpreaderRegion &mergee) + { + // Prevent grow_region from recursing while doing this + for (int x = mergee.x0; x <= mergee.x1; x++) { + for (int y = mergee.y0; y <= mergee.y1; y++) { + // log_info("%d %d\n", groups.at(x).at(y), mergee.id); + NPNR_ASSERT(groups.at(x).at(y) == mergee.id); + groups.at(x).at(y) = merged.id; + for (size_t t = 0; t < buckets.size(); t++) { + merged.cells.at(t) += occ_at(x, y, t); + merged.bels.at(t) += bels_at(x, y, t); + } + } + } + + merged_regions.insert(mergee.id); + grow_region(merged, mergee.x0, mergee.y0, mergee.x1, mergee.y1); + } + + void grow_region(SpreaderRegion &r, int x0, int y0, int x1, int y1, bool init = false) + { + // log_info("growing to (%d, %d) |_> (%d, %d)\n", x0, y0, x1, y1); + if ((x0 >= r.x0 && y0 >= r.y0 && x1 <= r.x1 && y1 <= r.y1) || init) + return; + int old_x0 = r.x0 + (init ? 1 : 0), old_y0 = r.y0, old_x1 = r.x1, old_y1 = r.y1; + r.x0 = std::min(r.x0, x0); + r.y0 = std::min(r.y0, y0); + r.x1 = std::max(r.x1, x1); + r.y1 = std::max(r.y1, y1); + + auto process_location = [&](int x, int y) { + // Merge with any overlapping regions + if (groups.at(x).at(y) == -1) { + for (size_t t = 0; t < buckets.size(); t++) { + r.bels.at(t) += bels_at(x, y, t); + r.cells.at(t) += occ_at(x, y, t); + } + } + + if (groups.at(x).at(y) != -1 && groups.at(x).at(y) != r.id) + merge_regions(r, regions.at(groups.at(x).at(y))); + groups.at(x).at(y) = r.id; + // Grow to cover any chains + auto &chaine = chaines.at(x).at(y); + grow_region(r, chaine.x0, chaine.y0, chaine.x1, chaine.y1); + }; + for (int x = r.x0; x < old_x0; x++) + for (int y = r.y0; y <= r.y1; y++) + process_location(x, y); + for (int x = old_x1 + 1; x <= x1; x++) + for (int y = r.y0; y <= r.y1; y++) + process_location(x, y); + for (int y = r.y0; y < old_y0; y++) + for (int x = r.x0; x <= r.x1; x++) + process_location(x, y); + for (int y = old_y1 + 1; y <= r.y1; y++) + for (int x = r.x0; x <= r.x1; x++) + process_location(x, y); + } + + void find_overused_regions() + { + for (int x = 0; x <= p->max_x; x++) + for (int y = 0; y <= p->max_y; y++) { + // Either already in a group, or not overutilised. Ignore + if (groups.at(x).at(y) != -1) + continue; + bool overutilised = false; + for (size_t t = 0; t < buckets.size(); t++) { + if (occ_at(x, y, t) > bels_at(x, y, t)) { + overutilised = true; + break; + } + } + + if (!overutilised) + continue; + // log_info("%d %d %d\n", x, y, occ_at(x, y)); + int id = int(regions.size()); + groups.at(x).at(y) = id; + SpreaderRegion reg; + reg.id = id; + reg.x0 = reg.x1 = x; + reg.y0 = reg.y1 = y; + for (size_t t = 0; t < buckets.size(); t++) { + reg.bels.push_back(bels_at(x, y, t)); + reg.cells.push_back(occ_at(x, y, t)); + } + // Make sure we cover carries, etc + grow_region(reg, reg.x0, reg.y0, reg.x1, reg.y1, true); + + bool expanded = true; + while (expanded) { + expanded = false; + // Keep trying expansion in x and y, until we find no over-occupancy cells + // or hit grouped cells + + // First try expanding in x + if (reg.x1 < p->max_x) { + bool over_occ_x = false; + for (int y1 = reg.y0; y1 <= reg.y1; y1++) { + for (size_t t = 0; t < buckets.size(); t++) { + if (occ_at(reg.x1 + 1, y1, t) > bels_at(reg.x1 + 1, y1, t)) { + over_occ_x = true; + break; + } + } + } + if (over_occ_x) { + expanded = true; + grow_region(reg, reg.x0, reg.y0, reg.x1 + 1, reg.y1); + } + } + + if (reg.y1 < p->max_y) { + bool over_occ_y = false; + for (int x1 = reg.x0; x1 <= reg.x1; x1++) { + for (size_t t = 0; t < buckets.size(); t++) { + if (occ_at(x1, reg.y1 + 1, t) > bels_at(x1, reg.y1 + 1, t)) { + over_occ_y = true; + break; + } + } + } + if (over_occ_y) { + expanded = true; + grow_region(reg, reg.x0, reg.y0, reg.x1, reg.y1 + 1); + } + } + } + regions.push_back(reg); + } + } + + void expand_regions() + { + std::queue overu_regions; + float beta = p->cfg.beta; + for (auto &r : regions) { + if (!merged_regions.count(r.id) && r.overused(beta)) + overu_regions.push(r.id); + } + while (!overu_regions.empty()) { + int rid = overu_regions.front(); + overu_regions.pop(); + if (merged_regions.count(rid)) + continue; + auto ® = regions.at(rid); + while (reg.overused(beta)) { + bool changed = false; + for (int j = 0; j < p->cfg.spread_scale_x; j++) { + if (reg.x0 > 0) { + grow_region(reg, reg.x0 - 1, reg.y0, reg.x1, reg.y1); + changed = true; + if (!reg.overused(beta)) + break; + } + if (reg.x1 < p->max_x) { + grow_region(reg, reg.x0, reg.y0, reg.x1 + 1, reg.y1); + changed = true; + if (!reg.overused(beta)) + break; + } + } + for (int j = 0; j < p->cfg.spread_scale_y; j++) { + if (reg.y0 > 0) { + grow_region(reg, reg.x0, reg.y0 - 1, reg.x1, reg.y1); + changed = true; + if (!reg.overused(beta)) + break; + } + if (reg.y1 < p->max_y) { + grow_region(reg, reg.x0, reg.y0, reg.x1, reg.y1 + 1); + changed = true; + if (!reg.overused(beta)) + break; + } + } + if (!changed) { + for (auto bucket : buckets) { + if (reg.cells > reg.bels) { + IdString bucket_name = ctx->getBelBucketName(bucket); + log_error("Failed to expand region (%d, %d) |_> (%d, %d) of %d %ss\n", reg.x0, reg.y0, + reg.x1, reg.y1, reg.cells.at(type_index.at(bucket)), bucket_name.c_str(ctx)); + } + } + break; + } + } + } + } + + // Implementation of the recursive cut-based spreading as described in the HeAP paper + // Note we use "left" to mean "-x/-y" depending on dir and "right" to mean "+x/+y" depending on dir + + std::vector cut_cells; + + boost::optional> cut_region(SpreaderRegion &r, bool dir) + { + cut_cells.clear(); + auto &cal = cells_at_location; + int total_cells = 0, total_bels = 0; + for (int x = r.x0; x <= r.x1; x++) { + for (int y = r.y0; y <= r.y1; y++) { + std::copy(cal.at(x).at(y).begin(), cal.at(x).at(y).end(), std::back_inserter(cut_cells)); + for (size_t t = 0; t < buckets.size(); t++) + total_bels += bels_at(x, y, t); + } + } + for (auto &cell : cut_cells) { + total_cells += p->chain_size.count(cell->name) ? p->chain_size.at(cell->name) : 1; + } + std::sort(cut_cells.begin(), cut_cells.end(), [&](const CellInfo *a, const CellInfo *b) { + return dir ? (p->cell_locs.at(a->name).rawy < p->cell_locs.at(b->name).rawy) + : (p->cell_locs.at(a->name).rawx < p->cell_locs.at(b->name).rawx); + }); + + if (cut_cells.size() < 2) + return {}; + // Find the cells midpoint, counting chains in terms of their total size - making the initial source cut + int pivot_cells = 0; + int pivot = 0; + for (auto &cell : cut_cells) { + pivot_cells += p->chain_size.count(cell->name) ? p->chain_size.at(cell->name) : 1; + if (pivot_cells >= total_cells / 2) + break; + pivot++; + } + if (pivot >= int(cut_cells.size())) { + pivot = int(cut_cells.size()) - 1; + } + // log_info("orig pivot %d/%d lc %d rc %d\n", pivot, int(cut_cells.size()), pivot_cells, total_cells - + // pivot_cells); + + // Find the clearance required either side of the pivot + int clearance_l = 0, clearance_r = 0; + for (size_t i = 0; i < cut_cells.size(); i++) { + int size; + if (cell_extents.count(cut_cells.at(i)->name)) { + auto &ce = cell_extents.at(cut_cells.at(i)->name); + size = dir ? (ce.y1 - ce.y0 + 1) : (ce.x1 - ce.x0 + 1); + } else { + size = 1; + } + if (int(i) < pivot) + clearance_l = std::max(clearance_l, size); + else + clearance_r = std::max(clearance_r, size); + } + // Find the target cut that minimises difference in utilisation, whilst trying to ensure that all chains + // still fit + + // First trim the boundaries of the region in the axis-of-interest, skipping any rows/cols without any + // bels of the appropriate type + int trimmed_l = dir ? r.y0 : r.x0, trimmed_r = dir ? r.y1 : r.x1; + while (trimmed_l < (dir ? r.y1 : r.x1)) { + bool have_bels = false; + for (int i = dir ? r.x0 : r.y0; i <= (dir ? r.x1 : r.y1); i++) { + for (size_t t = 0; t < buckets.size(); t++) { + if (bels_at(dir ? i : trimmed_l, dir ? trimmed_l : i, t) > 0) { + have_bels = true; + break; + } + } + } + + if (have_bels) + break; + + trimmed_l++; + } + while (trimmed_r > (dir ? r.y0 : r.x0)) { + bool have_bels = false; + for (int i = dir ? r.x0 : r.y0; i <= (dir ? r.x1 : r.y1); i++) { + for (size_t t = 0; t < buckets.size(); t++) { + if (bels_at(dir ? i : trimmed_r, dir ? trimmed_r : i, t) > 0) { + have_bels = true; + break; + } + } + } + + if (have_bels) + break; + + trimmed_r--; + } + // log_info("tl %d tr %d cl %d cr %d\n", trimmed_l, trimmed_r, clearance_l, clearance_r); + if ((trimmed_r - trimmed_l + 1) <= std::max(clearance_l, clearance_r)) + return {}; + // Now find the initial target cut that minimises utilisation imbalance, whilst + // meeting the clearance requirements for any large macros + std::vector left_cells_v(buckets.size(), 0), right_cells_v(buckets.size(), 0); + std::vector left_bels_v(buckets.size(), 0), right_bels_v(r.bels); + for (int i = 0; i <= pivot; i++) + left_cells_v.at(cell_index(*cut_cells.at(i))) += + p->chain_size.count(cut_cells.at(i)->name) ? p->chain_size.at(cut_cells.at(i)->name) : 1; + for (int i = pivot + 1; i < int(cut_cells.size()); i++) + right_cells_v.at(cell_index(*cut_cells.at(i))) += + p->chain_size.count(cut_cells.at(i)->name) ? p->chain_size.at(cut_cells.at(i)->name) : 1; + + int best_tgt_cut = -1; + double best_deltaU = std::numeric_limits::max(); + // std::pair target_cut_bels; + std::vector slither_bels(buckets.size(), 0); + for (int i = trimmed_l; i <= trimmed_r; i++) { + for (size_t t = 0; t < buckets.size(); t++) + slither_bels.at(t) = 0; + for (int j = dir ? r.x0 : r.y0; j <= (dir ? r.x1 : r.y1); j++) { + for (size_t t = 0; t < buckets.size(); t++) + slither_bels.at(t) += dir ? bels_at(j, i, t) : bels_at(i, j, t); + } + for (size_t t = 0; t < buckets.size(); t++) { + left_bels_v.at(t) += slither_bels.at(t); + right_bels_v.at(t) -= slither_bels.at(t); + } + + if (((i - trimmed_l) + 1) >= clearance_l && ((trimmed_r - i) + 1) >= clearance_r) { + // Solution is potentially valid + double aU = 0; + for (size_t t = 0; t < buckets.size(); t++) + aU += (left_cells_v.at(t) + right_cells_v.at(t)) * + std::abs(double(left_cells_v.at(t)) / double(std::max(left_bels_v.at(t), 1)) - + double(right_cells_v.at(t)) / double(std::max(right_bels_v.at(t), 1))); + if (aU < best_deltaU) { + best_deltaU = aU; + best_tgt_cut = i; + } + } + } + if (best_tgt_cut == -1) + return {}; + // left_bels = target_cut_bels.first; + // right_bels = target_cut_bels.second; + for (size_t t = 0; t < buckets.size(); t++) { + left_bels_v.at(t) = 0; + right_bels_v.at(t) = 0; + } + for (int x = r.x0; x <= (dir ? r.x1 : best_tgt_cut); x++) + for (int y = r.y0; y <= (dir ? best_tgt_cut : r.y1); y++) { + for (size_t t = 0; t < buckets.size(); t++) { + left_bels_v.at(t) += bels_at(x, y, t); + } + } + for (int x = dir ? r.x0 : (best_tgt_cut + 1); x <= r.x1; x++) + for (int y = dir ? (best_tgt_cut + 1) : r.y0; y <= r.y1; y++) { + for (size_t t = 0; t < buckets.size(); t++) { + right_bels_v.at(t) += bels_at(x, y, t); + } + } + if (std::accumulate(left_bels_v.begin(), left_bels_v.end(), 0) == 0 || + std::accumulate(right_bels_v.begin(), right_bels_v.end(), 0) == 0) + return {}; + + // Perturb the source cut to eliminate overutilisation + auto is_part_overutil = [&](bool r) { + double delta = 0; + for (size_t t = 0; t < left_cells_v.size(); t++) { + delta += double(left_cells_v.at(t)) / double(std::max(left_bels_v.at(t), 1)) - + double(right_cells_v.at(t)) / double(std::max(right_bels_v.at(t), 1)); + } + return r ? delta < 0 : delta > 0; + }; + while (pivot > 0 && is_part_overutil(false)) { + auto &move_cell = cut_cells.at(pivot); + int size = p->chain_size.count(move_cell->name) ? p->chain_size.at(move_cell->name) : 1; + left_cells_v.at(cell_index(*cut_cells.at(pivot))) -= size; + right_cells_v.at(cell_index(*cut_cells.at(pivot))) += size; + pivot--; + } + while (pivot < int(cut_cells.size()) - 1 && is_part_overutil(true)) { + auto &move_cell = cut_cells.at(pivot + 1); + int size = p->chain_size.count(move_cell->name) ? p->chain_size.at(move_cell->name) : 1; + left_cells_v.at(cell_index(*cut_cells.at(pivot))) += size; + right_cells_v.at(cell_index(*cut_cells.at(pivot))) -= size; + pivot++; + } + + // Split regions into bins, and then spread cells by linear interpolation within those bins + auto spread_binlerp = [&](int cells_start, int cells_end, double area_l, double area_r) { + int N = cells_end - cells_start; + if (N <= 2) { + for (int i = cells_start; i < cells_end; i++) { + auto &pos = dir ? p->cell_locs.at(cut_cells.at(i)->name).rawy + : p->cell_locs.at(cut_cells.at(i)->name).rawx; + pos = area_l + i * ((area_r - area_l) / N); + } + return; + } + // Split region into up to 10 (K) bins + int K = std::min(N, 10); + std::vector> bin_bounds; // [(cell start, area start)] + bin_bounds.emplace_back(cells_start, area_l); + for (int i = 1; i < K; i++) + bin_bounds.emplace_back(cells_start + (N * i) / K, area_l + ((area_r - area_l + 0.99) * i) / K); + bin_bounds.emplace_back(cells_end, area_r + 0.99); + for (int i = 0; i < K; i++) { + auto &bl = bin_bounds.at(i), br = bin_bounds.at(i + 1); + double orig_left = dir ? p->cell_locs.at(cut_cells.at(bl.first)->name).rawy + : p->cell_locs.at(cut_cells.at(bl.first)->name).rawx; + double orig_right = dir ? p->cell_locs.at(cut_cells.at(br.first - 1)->name).rawy + : p->cell_locs.at(cut_cells.at(br.first - 1)->name).rawx; + double m = (br.second - bl.second) / std::max(0.00001, orig_right - orig_left); + for (int j = bl.first; j < br.first; j++) { + Region *cr = cut_cells.at(j)->region; + if (cr != nullptr) { + // Limit spreading bounds to constraint region; if applicable + double brsc = p->limit_to_reg(cr, br.second, dir); + double blsc = p->limit_to_reg(cr, bl.second, dir); + double mr = (brsc - blsc) / std::max(0.00001, orig_right - orig_left); + auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy + : p->cell_locs.at(cut_cells.at(j)->name).rawx; + NPNR_ASSERT(pos >= orig_left && pos <= orig_right); + pos = blsc + mr * (pos - orig_left); + } else { + auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy + : p->cell_locs.at(cut_cells.at(j)->name).rawx; + NPNR_ASSERT(pos >= orig_left && pos <= orig_right); + pos = bl.second + m * (pos - orig_left); + } + } + } + }; + spread_binlerp(0, pivot + 1, trimmed_l, best_tgt_cut); + spread_binlerp(pivot + 1, int(cut_cells.size()), best_tgt_cut + 1, trimmed_r); + // Update various data structures + for (int x = r.x0; x <= r.x1; x++) + for (int y = r.y0; y <= r.y1; y++) { + cells_at_location.at(x).at(y).clear(); + } + for (auto cell : cut_cells) { + auto &cl = p->cell_locs.at(cell->name); + cl.x = std::min(r.x1, std::max(r.x0, int(cl.rawx))); + cl.y = std::min(r.y1, std::max(r.y0, int(cl.rawy))); + cells_at_location.at(cl.x).at(cl.y).push_back(cell); + } + SpreaderRegion rl, rr; + rl.id = int(regions.size()); + rl.x0 = r.x0; + rl.y0 = r.y0; + rl.x1 = dir ? r.x1 : best_tgt_cut; + rl.y1 = dir ? best_tgt_cut : r.y1; + rl.cells = left_cells_v; + rl.bels = left_bels_v; + rr.id = int(regions.size()) + 1; + rr.x0 = dir ? r.x0 : (best_tgt_cut + 1); + rr.y0 = dir ? (best_tgt_cut + 1) : r.y0; + rr.x1 = r.x1; + rr.y1 = r.y1; + rr.cells = right_cells_v; + rr.bels = right_bels_v; + regions.push_back(rl); + regions.push_back(rr); + for (int x = rl.x0; x <= rl.x1; x++) + for (int y = rl.y0; y <= rl.y1; y++) + groups.at(x).at(y) = rl.id; + for (int x = rr.x0; x <= rr.x1; x++) + for (int y = rr.y0; y <= rr.y1; y++) + groups.at(x).at(y) = rr.id; + return std::make_pair(rl.id, rr.id); + }; + }; + typedef decltype(CellInfo::udata) cell_udata_t; + cell_udata_t dont_solve = std::numeric_limits::max(); +}; +int HeAPPlacer::CutSpreader::seq = 0; + +bool placer_heap(Context *ctx, PlacerHeapCfg cfg) { return HeAPPlacer(ctx, cfg).place(); } + +PlacerHeapCfg::PlacerHeapCfg(Context *ctx) +{ + alpha = ctx->setting("placerHeap/alpha"); + beta = ctx->setting("placerHeap/beta"); + criticalityExponent = ctx->setting("placerHeap/criticalityExponent"); + timingWeight = ctx->setting("placerHeap/timingWeight"); + parallelRefine = ctx->setting("placerHeap/parallelRefine", false); + + timing_driven = ctx->setting("timing_driven"); + solverTolerance = 1e-5; + placeAllAtOnce = false; + + hpwl_scale_x = 1; + hpwl_scale_y = 1; + spread_scale_x = 1; + spread_scale_y = 1; +} + +NEXTPNR_NAMESPACE_END + +#else + +#include "log.h" +#include "nextpnr.h" +#include "placer_heap.h" + +NEXTPNR_NAMESPACE_BEGIN +bool placer_heap(Context *ctx, PlacerHeapCfg cfg) +{ + log_error("nextpnr was built without the HeAP placer\n"); + return false; +} + +PlacerHeapCfg::PlacerHeapCfg(Context *ctx) {} + +NEXTPNR_NAMESPACE_END + +#endif diff --git a/common/place/placer_heap.h b/common/place/placer_heap.h new file mode 100644 index 00000000..9c62869e --- /dev/null +++ b/common/place/placer_heap.h @@ -0,0 +1,58 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 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. + * + * [[cite]] HeAP + * Analytical Placement for Heterogeneous FPGAs, Marcel Gort and Jason H. Anderson + * https://janders.eecg.utoronto.ca/pdfs/marcelfpl12.pdf + * + * [[cite]] SimPL + * SimPL: An Effective Placement Algorithm, Myung-Chul Kim, Dong-Jin Lee and Igor L. Markov + * http://www.ece.umich.edu/cse/awards/pdfs/iccad10-simpl.pdf + */ + +#ifndef PLACER_HEAP_H +#define PLACER_HEAP_H +#include "log.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct PlacerHeapCfg +{ + PlacerHeapCfg(Context *ctx); + + float alpha, beta; + float criticalityExponent; + float timingWeight; + bool timing_driven; + float solverTolerance; + bool placeAllAtOnce; + bool parallelRefine; + + int hpwl_scale_x, hpwl_scale_y; + int spread_scale_x, spread_scale_y; + + // These cell types will be randomly locked to prevent singular matrices + pool ioBufTypes; + // These cell types are part of the same unit (e.g. slices split into + // components) so will always be spread together + std::vector> cellGroups; +}; + +extern bool placer_heap(Context *ctx, PlacerHeapCfg cfg); +NEXTPNR_NAMESPACE_END +#endif diff --git a/common/place/timing_opt.cc b/common/place/timing_opt.cc new file mode 100644 index 00000000..f9246292 --- /dev/null +++ b/common/place/timing_opt.cc @@ -0,0 +1,561 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * 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. + * + */ + +/* + * Timing-optimised detailed placement algorithm using BFS of the neighbour graph created from cells + * on a critical path + * + * Based on "An Effective Timing-Driven Detailed Placement Algorithm for FPGAs" + * https://www.cerc.utexas.edu/utda/publications/C205.pdf + * + * Modifications made to deal with the smaller Bels that nextpnr uses instead of swapping whole tiles, + * and deal with the fact that not every cell on the crit path may be swappable. + */ + +#include "timing_opt.h" +#include +#include +#include "nextpnr.h" +#include "timing.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +class TimingOptimiser +{ + public: + TimingOptimiser(Context *ctx, TimingOptCfg cfg) : ctx(ctx), cfg(cfg), tmg(ctx){}; + bool optimise() + { + log_info("Running timing-driven placement optimisation...\n"); + ctx->lock(); + if (ctx->verbose) + timing_analysis(ctx, false, true, false, false); + tmg.setup(); + for (int i = 0; i < 30; i++) { + log_info(" Iteration %d...\n", i); + tmg.run(); + setup_delay_limits(); + auto crit_paths = find_crit_paths(0.98, 50000); + for (auto &path : crit_paths) + optimise_path(path); + if (ctx->verbose) + timing_analysis(ctx, false, true, false, false); + } + ctx->unlock(); + return true; + } + + private: + void setup_delay_limits() + { + max_net_delay.clear(); + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (ni->driver.cell == nullptr) + continue; + for (auto usr : ni->users) { + max_net_delay[std::make_pair(usr.cell->name, usr.port)] = std::numeric_limits::max(); + } + for (auto usr : ni->users) { + delay_t net_delay = ctx->getNetinfoRouteDelay(ni, usr); + delay_t slack = tmg.get_setup_slack(CellPortKey(usr)); + delay_t domain_slack = tmg.get_domain_setup_slack(CellPortKey(usr)); + if (slack == std::numeric_limits::max()) + continue; + max_net_delay[std::make_pair(usr.cell->name, usr.port)] = net_delay + ((slack - domain_slack) / 10); + } + } + } + + bool check_cell_delay_limits(CellInfo *cell) + { + for (const auto &port : cell->ports) { + int nc; + if (ctx->getPortTimingClass(cell, port.first, nc) == TMG_IGNORE) + continue; + NetInfo *net = port.second.net; + if (net == nullptr) + continue; + if (port.second.type == PORT_IN) { + if (net->driver.cell == nullptr || net->driver.cell->bel == BelId()) + continue; + for (auto user : net->users) { + if (user.cell == cell && user.port == port.first) { + if (ctx->predictArcDelay(net, user) > + 1.1 * max_net_delay.at(std::make_pair(cell->name, port.first))) + return false; + } + } + + } else if (port.second.type == PORT_OUT) { + for (auto user : net->users) { + // This could get expensive for high-fanout nets?? + BelId dstBel = user.cell->bel; + if (dstBel == BelId()) + continue; + if (ctx->predictArcDelay(net, user) > + 1.1 * max_net_delay.at(std::make_pair(user.cell->name, user.port))) { + + return false; + } + } + } + } + return true; + } + + BelId cell_swap_bel(CellInfo *cell, BelId newBel) + { + BelId oldBel = cell->bel; + if (oldBel == newBel) + return oldBel; + CellInfo *other_cell = ctx->getBoundBelCell(newBel); + NPNR_ASSERT(other_cell == nullptr || other_cell->belStrength <= STRENGTH_WEAK); + ctx->unbindBel(oldBel); + if (other_cell != nullptr) { + ctx->unbindBel(newBel); + ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK); + } + ctx->bindBel(newBel, cell, STRENGTH_WEAK); + return oldBel; + } + + // Check that a series of moves are both legal and remain within maximum delay bounds + // Moves are specified as a vector of pairs + bool acceptable_move(std::vector> &move, bool check_delays = true) + { + for (auto &entry : move) { + if (!ctx->isBelLocationValid(entry.first->bel)) + return false; + if (!ctx->isBelLocationValid(entry.second)) + return false; + if (!check_delays) + continue; + if (!check_cell_delay_limits(entry.first)) + return false; + // We might have swapped another cell onto the original bel. Check this for max delay violations + // too + CellInfo *swapped = ctx->getBoundBelCell(entry.second); + if (swapped != nullptr && !check_cell_delay_limits(swapped)) + return false; + } + return true; + } + + int find_neighbours(CellInfo *cell, IdString prev_cell, int d, bool allow_swap) + { + BelId curr = cell->bel; + Loc curr_loc = ctx->getBelLocation(curr); + int found_count = 0; + cell_neighbour_bels[cell->name] = pool{}; + for (int dy = -d; dy <= d; dy++) { + for (int dx = -d; dx <= d; dx++) { + // Go through all the Bels at this location + // First, find all bels of the correct type that are either unbound or bound normally + // Strongly bound bels are ignored + // FIXME: This means that we cannot touch carry chains or similar relatively constrained macros + std::vector free_bels_at_loc; + std::vector bound_bels_at_loc; + for (auto bel : ctx->getBelsByTile(curr_loc.x + dx, curr_loc.y + dy)) { + if (!ctx->isValidBelForCellType(cell->type, bel)) + continue; + CellInfo *bound = ctx->getBoundBelCell(bel); + if (bound == nullptr) { + free_bels_at_loc.push_back(bel); + } else if (bound->belStrength <= STRENGTH_WEAK && bound->cluster == ClusterId()) { + bound_bels_at_loc.push_back(bel); + } + } + BelId candidate; + + while (!free_bels_at_loc.empty() || !bound_bels_at_loc.empty()) { + BelId try_bel; + if (!free_bels_at_loc.empty()) { + int try_idx = ctx->rng(int(free_bels_at_loc.size())); + try_bel = free_bels_at_loc.at(try_idx); + free_bels_at_loc.erase(free_bels_at_loc.begin() + try_idx); + } else { + int try_idx = ctx->rng(int(bound_bels_at_loc.size())); + try_bel = bound_bels_at_loc.at(try_idx); + bound_bels_at_loc.erase(bound_bels_at_loc.begin() + try_idx); + } + if (bel_candidate_cells.count(try_bel) && !allow_swap) { + // Overlap is only allowed if it is with the previous cell (this is handled by removing those + // edges in the graph), or if allow_swap is true to deal with cases where overlap means few + // neighbours are identified + if (bel_candidate_cells.at(try_bel).size() > 1 || + (bel_candidate_cells.at(try_bel).size() == 1 && + *(bel_candidate_cells.at(try_bel).begin()) != prev_cell)) + continue; + } + // TODO: what else to check here? + candidate = try_bel; + break; + } + + if (candidate != BelId()) { + cell_neighbour_bels[cell->name].insert(candidate); + bel_candidate_cells[candidate].insert(cell->name); + // Work out if we need to delete any overlap + std::vector overlap; + for (auto other : bel_candidate_cells[candidate]) + if (other != cell->name && other != prev_cell) + overlap.push_back(other); + if (overlap.size() > 0) + NPNR_ASSERT(allow_swap); + for (auto ov : overlap) { + bel_candidate_cells[candidate].erase(ov); + cell_neighbour_bels[ov].erase(candidate); + } + } + } + } + return found_count; + } + + std::vector> find_crit_paths(float crit_thresh, size_t max_count) + { + std::vector> crit_paths; + std::vector>> crit_nets; + std::vector netnames; + std::transform(ctx->nets.begin(), ctx->nets.end(), std::back_inserter(netnames), + [](const std::pair> &kv) { return kv.first; }); + ctx->sorted_shuffle(netnames); + for (auto net : netnames) { + if (crit_nets.size() >= max_count) + break; + float highest_crit = 0; + store_index crit_user_idx{}; + NetInfo *ni = ctx->nets.at(net).get(); + for (auto usr : ni->users.enumerate()) { + float crit = tmg.get_criticality(CellPortKey(usr.value)); + if (crit > highest_crit) { + highest_crit = crit; + crit_user_idx = usr.index; + } + } + if (highest_crit > crit_thresh) + crit_nets.emplace_back(ni, crit_user_idx); + } + + pool used_ports; + + for (auto crit_net : crit_nets) { + + if (used_ports.count(&(crit_net.first->users.at(crit_net.second)))) + continue; + + std::deque crit_path; + + // FIXME: This will fail badly on combinational loops + + // Iterate backwards following greatest criticality + NetInfo *back_cursor = crit_net.first; + while (back_cursor != nullptr) { + float max_crit = 0; + std::pair> crit_sink{nullptr, {}}; + CellInfo *cell = back_cursor->driver.cell; + if (cell == nullptr) + break; + for (auto port : cell->ports) { + if (port.second.type != PORT_IN) + continue; + NetInfo *pn = port.second.net; + if (pn == nullptr) + continue; + int ccount; + DelayQuad combDelay; + TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount); + if (tpclass != TMG_COMB_INPUT) + continue; + bool is_path = ctx->getCellDelay(cell, port.first, back_cursor->driver.port, combDelay); + if (!is_path) + continue; + float usr_crit = tmg.get_criticality(CellPortKey(cell->name, port.first)); + if (used_ports.count(&(pn->users.at(port.second.user_idx)))) + continue; + if (usr_crit >= max_crit) { + max_crit = usr_crit; + crit_sink = std::make_pair(pn, port.second.user_idx); + } + } + + if (crit_sink.first != nullptr) { + crit_path.push_front(&(crit_sink.first->users.at(crit_sink.second))); + used_ports.insert(&(crit_sink.first->users.at(crit_sink.second))); + } + back_cursor = crit_sink.first; + } + // Iterate forwards following greatest criticiality + PortRef *fwd_cursor = &(crit_net.first->users.at(crit_net.second)); + while (fwd_cursor != nullptr) { + crit_path.push_back(fwd_cursor); + float max_crit = 0; + std::pair> crit_sink{nullptr, {}}; + CellInfo *cell = fwd_cursor->cell; + for (auto port : cell->ports) { + if (port.second.type != PORT_OUT) + continue; + NetInfo *pn = port.second.net; + if (pn == nullptr) + continue; + + int ccount; + DelayQuad combDelay; + TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount); + if (tpclass != TMG_COMB_OUTPUT && tpclass != TMG_REGISTER_OUTPUT) + continue; + bool is_path = ctx->getCellDelay(cell, fwd_cursor->port, port.first, combDelay); + if (!is_path) + continue; + for (auto usr : pn->users.enumerate()) { + if (used_ports.count(&(pn->users.at(usr.index)))) + continue; + float crit = tmg.get_criticality(CellPortKey(usr.value)); + if (crit >= max_crit) { + max_crit = crit; + crit_sink = std::make_pair(pn, usr.index); + } + } + } + if (crit_sink.first != nullptr) { + fwd_cursor = &(crit_sink.first->users.at(crit_sink.second)); + used_ports.insert(&(crit_sink.first->users.at(crit_sink.second))); + } else { + fwd_cursor = nullptr; + } + } + + std::vector crit_path_vec; + std::copy(crit_path.begin(), crit_path.end(), std::back_inserter(crit_path_vec)); + crit_paths.push_back(crit_path_vec); + } + + return crit_paths; + } + + void optimise_path(std::vector &path) + { + path_cells.clear(); + cell_neighbour_bels.clear(); + bel_candidate_cells.clear(); + if (ctx->debug) + log_info("Optimising the following path: \n"); + + auto front_port = path.front(); + NetInfo *front_net = front_port->cell->ports.at(front_port->port).net; + if (front_net != nullptr && front_net->driver.cell != nullptr) { + auto front_cell = front_net->driver.cell; + if (front_cell->belStrength <= STRENGTH_WEAK && cfg.cellTypes.count(front_cell->type) && + front_cell->cluster == ClusterId()) { + path_cells.push_back(front_cell->name); + } + } + + for (auto port : path) { + if (ctx->debug) { + float crit = tmg.get_criticality(CellPortKey(*port)); + log_info(" %s.%s at %s crit %0.02f\n", port->cell->name.c_str(ctx), port->port.c_str(ctx), + ctx->nameOfBel(port->cell->bel), crit); + } + if (std::find(path_cells.begin(), path_cells.end(), port->cell->name) != path_cells.end()) + continue; + if (port->cell->belStrength > STRENGTH_WEAK || !cfg.cellTypes.count(port->cell->type) || + port->cell->cluster != ClusterId()) + continue; + if (ctx->debug) + log_info(" can move\n"); + path_cells.push_back(port->cell->name); + } + + if (path_cells.size() < 2) { + if (ctx->debug) { + log_info("Too few moveable cells; skipping path\n"); + log_break(); + } + + return; + } + + // Calculate original delay before touching anything + delay_t original_delay = 0; + + for (size_t i = 0; i < path.size(); i++) { + auto &port = path.at(i)->cell->ports.at(path.at(i)->port); + NetInfo *pn = port.net; + if (port.user_idx) + original_delay += ctx->predictArcDelay(pn, pn->users.at(port.user_idx)); + } + + IdString last_cell; + const int d = 2; // FIXME: how to best determine d + for (auto cell : path_cells) { + // FIXME: when should we allow swapping due to a lack of candidates + find_neighbours(ctx->cells[cell].get(), last_cell, d, false); + last_cell = cell; + } + + if (ctx->debug) { + for (auto cell : path_cells) { + log_info("Candidate neighbours for %s (%s):\n", cell.c_str(ctx), ctx->nameOfBel(ctx->cells[cell]->bel)); + for (auto neigh : cell_neighbour_bels.at(cell)) { + log_info(" %s\n", ctx->nameOfBel(neigh)); + } + } + } + + // Actual BFS path optimisation algorithm + dict> cumul_costs; + dict, std::pair> backtrace; + std::queue> visit; + pool> to_visit; + + for (auto startbel : cell_neighbour_bels[path_cells.front()]) { + // Swap for legality check + CellInfo *cell = ctx->cells.at(path_cells.front()).get(); + BelId origBel = cell_swap_bel(cell, startbel); + std::vector> move{std::make_pair(cell, origBel)}; + if (acceptable_move(move)) { + auto entry = std::make_pair(0, startbel); + visit.push(entry); + cumul_costs[path_cells.front()][startbel] = 0; + } + // Swap back + cell_swap_bel(cell, origBel); + } + + while (!visit.empty()) { + auto entry = visit.front(); + visit.pop(); + auto cellname = path_cells.at(entry.first); + if (entry.first == int(path_cells.size()) - 1) + continue; + std::vector> move; + // Apply the entire backtrace for accurate legality and delay checks + // This is probably pretty expensive (but also probably pales in comparison to the number of swaps + // SA will make...) + std::vector> route_to_entry; + auto cursor = std::make_pair(cellname, entry.second); + route_to_entry.push_back(cursor); + while (backtrace.count(cursor)) { + cursor = backtrace.at(cursor); + route_to_entry.push_back(cursor); + } + for (auto rt_entry : boost::adaptors::reverse(route_to_entry)) { + CellInfo *cell = ctx->cells.at(rt_entry.first).get(); + BelId origBel = cell_swap_bel(cell, rt_entry.second); + move.push_back(std::make_pair(cell, origBel)); + } + + // Have a look at where we can travel from here + for (auto neighbour : cell_neighbour_bels.at(path_cells.at(entry.first + 1))) { + // Edges between overlapping bels are deleted + if (neighbour == entry.second) + continue; + // Experimentally swap the next path cell onto the neighbour bel we are trying + IdString ncname = path_cells.at(entry.first + 1); + CellInfo *next_cell = ctx->cells.at(ncname).get(); + BelId origBel = cell_swap_bel(next_cell, neighbour); + move.push_back(std::make_pair(next_cell, origBel)); + + delay_t total_delay = 0; + + for (size_t i = 0; i < path.size(); i++) { + auto &port = path.at(i)->cell->ports.at(path.at(i)->port); + NetInfo *pn = port.net; + if (port.user_idx) + total_delay += ctx->predictArcDelay(pn, pn->users.at(port.user_idx)); + if (path.at(i)->cell == next_cell) + break; + } + + // First, check if the move is actually worthwhile from a delay point of view before the expensive + // legality check + if (!cumul_costs.count(ncname) || !cumul_costs.at(ncname).count(neighbour) || + cumul_costs.at(ncname).at(neighbour) > total_delay) { + // Now check that the swaps we have made to get here are legal and meet max delay requirements + if (acceptable_move(move)) { + cumul_costs[ncname][neighbour] = total_delay; + backtrace[std::make_pair(ncname, neighbour)] = std::make_pair(cellname, entry.second); + if (!to_visit.count(std::make_pair(entry.first + 1, neighbour))) + visit.push(std::make_pair(entry.first + 1, neighbour)); + } + } + // Revert the experimental swap + cell_swap_bel(move.back().first, move.back().second); + move.pop_back(); + } + + // Revert move by swapping cells back to their original order + // Execute swaps in reverse order to how we made them originally + for (auto move_entry : boost::adaptors::reverse(move)) { + cell_swap_bel(move_entry.first, move_entry.second); + } + } + + // Did we find a solution?? + if (cumul_costs.count(path_cells.back())) { + // Find the end position with the lowest total delay + auto &end_options = cumul_costs.at(path_cells.back()); + auto lowest = std::min_element(end_options.begin(), end_options.end(), + [](const std::pair &a, const std::pair &b) { + return a.second < b.second; + }); + NPNR_ASSERT(lowest != end_options.end()); + + std::vector> route_to_solution; + auto cursor = std::make_pair(path_cells.back(), lowest->first); + route_to_solution.push_back(cursor); + while (backtrace.count(cursor)) { + cursor = backtrace.at(cursor); + route_to_solution.push_back(cursor); + } + if (ctx->debug) + log_info("Found a solution with cost %.02f ns (existing path %.02f ns)\n", + ctx->getDelayNS(lowest->second), ctx->getDelayNS(original_delay)); + for (auto rt_entry : boost::adaptors::reverse(route_to_solution)) { + CellInfo *cell = ctx->cells.at(rt_entry.first).get(); + cell_swap_bel(cell, rt_entry.second); + if (ctx->debug) + log_info(" %s at %s\n", rt_entry.first.c_str(ctx), ctx->nameOfBel(rt_entry.second)); + } + + } else { + if (ctx->debug) + log_info("Solution was not found\n"); + } + if (ctx->debug) + log_break(); + } + + // Current candidate Bels for cells (linked in both direction> + std::vector path_cells; + dict> cell_neighbour_bels; + dict> bel_candidate_cells; + // Map cell ports to net delay limit + dict, delay_t> max_net_delay; + Context *ctx; + TimingOptCfg cfg; + TimingAnalyser tmg; +}; + +bool timing_opt(Context *ctx, TimingOptCfg cfg) { return TimingOptimiser(ctx, cfg).optimise(); } + +NEXTPNR_NAMESPACE_END diff --git a/common/place/timing_opt.h b/common/place/timing_opt.h new file mode 100644 index 00000000..8f8bc709 --- /dev/null +++ b/common/place/timing_opt.h @@ -0,0 +1,37 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * 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. + * + */ + +#include "log.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct TimingOptCfg +{ + TimingOptCfg(Context *ctx) {} + + // The timing optimiser will *only* optimise cells of these types + // Normally these would only be logic cells (or tiles if applicable), the algorithm makes little sense + // for other cell types + pool cellTypes; +}; + +extern bool timing_opt(Context *ctx, TimingOptCfg cfg); + +NEXTPNR_NAMESPACE_END diff --git a/common/place_common.cc b/common/place_common.cc deleted file mode 100644 index e03fca55..00000000 --- a/common/place_common.cc +++ /dev/null @@ -1,487 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * 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. - * - */ - -#include "place_common.h" -#include -#include "log.h" -#include "util.h" - -NEXTPNR_NAMESPACE_BEGIN - -// Get the total estimated wirelength for a net -wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type, float &tns) -{ - wirelen_t wirelength = 0; - CellInfo *driver_cell = net->driver.cell; - if (!driver_cell) - return 0; - if (driver_cell->bel == BelId()) - return 0; - bool driver_gb = ctx->getBelGlobalBuf(driver_cell->bel); - if (driver_gb) - return 0; - int clock_count; - bool timing_driven = ctx->setting("timing_driven") && type == MetricType::COST && - ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) != TMG_IGNORE; - delay_t negative_slack = 0; - delay_t worst_slack = std::numeric_limits::max(); - Loc driver_loc = ctx->getBelLocation(driver_cell->bel); - int xmin = driver_loc.x, xmax = driver_loc.x, ymin = driver_loc.y, ymax = driver_loc.y; - for (auto load : net->users) { - if (load.cell == nullptr) - continue; - CellInfo *load_cell = load.cell; - if (load_cell->bel == BelId()) - continue; - if (timing_driven) { - delay_t net_delay = ctx->predictArcDelay(net, load); - auto slack = load.budget - net_delay; - if (slack < 0) - negative_slack += slack; - worst_slack = std::min(slack, worst_slack); - } - - if (ctx->getBelGlobalBuf(load_cell->bel)) - continue; - Loc load_loc = ctx->getBelLocation(load_cell->bel); - - xmin = std::min(xmin, load_loc.x); - ymin = std::min(ymin, load_loc.y); - xmax = std::max(xmax, load_loc.x); - ymax = std::max(ymax, load_loc.y); - } - if (timing_driven) { - wirelength = wirelen_t( - (((ymax - ymin) + (xmax - xmin)) * std::min(5.0, (1.0 + std::exp(-ctx->getDelayNS(worst_slack) / 5))))); - } else { - wirelength = wirelen_t((ymax - ymin) + (xmax - xmin)); - } - - tns += ctx->getDelayNS(negative_slack); - return wirelength; -} - -// Get the total wirelength for a cell -wirelen_t get_cell_metric(const Context *ctx, const CellInfo *cell, MetricType type) -{ - std::set nets; - for (auto p : cell->ports) { - if (p.second.net) - nets.insert(p.second.net->name); - } - wirelen_t wirelength = 0; - float tns = 0; - for (auto n : nets) { - wirelength += get_net_metric(ctx, ctx->nets.at(n).get(), type, tns); - } - return wirelength; -} - -wirelen_t get_cell_metric_at_bel(const Context *ctx, CellInfo *cell, BelId bel, MetricType type) -{ - BelId oldBel = cell->bel; - cell->bel = bel; - wirelen_t wirelen = get_cell_metric(ctx, cell, type); - cell->bel = oldBel; - return wirelen; -} - -// Placing a single cell -bool place_single_cell(Context *ctx, CellInfo *cell, bool require_legality) -{ - bool all_placed = false; - int iters = 25; - while (!all_placed) { - BelId best_bel = BelId(); - wirelen_t best_wirelen = std::numeric_limits::max(), - best_ripup_wirelen = std::numeric_limits::max(); - CellInfo *ripup_target = nullptr; - BelId ripup_bel = BelId(); - if (cell->bel != BelId()) { - ctx->unbindBel(cell->bel); - } - IdString targetType = cell->type; - for (auto bel : ctx->getBels()) { - if (ctx->isValidBelForCellType(targetType, bel)) { - if (ctx->checkBelAvail(bel)) { - wirelen_t wirelen = get_cell_metric_at_bel(ctx, cell, bel, MetricType::COST); - if (iters >= 4) - wirelen += ctx->rng(25); - if (wirelen <= best_wirelen) { - best_wirelen = wirelen; - best_bel = bel; - } - } else { - wirelen_t wirelen = get_cell_metric_at_bel(ctx, cell, bel, MetricType::COST); - if (iters >= 4) - wirelen += ctx->rng(25); - if (wirelen <= best_ripup_wirelen) { - CellInfo *curr_cell = ctx->getBoundBelCell(bel); - if (curr_cell->belStrength < STRENGTH_STRONG) { - best_ripup_wirelen = wirelen; - ripup_bel = bel; - ripup_target = curr_cell; - } - } - } - } - } - if (best_bel == BelId()) { - if (iters == 0) { - log_error("failed to place cell '%s' of type '%s' (ripup iteration limit exceeded)\n", - cell->name.c_str(ctx), cell->type.c_str(ctx)); - } - if (ripup_bel == BelId()) { - log_error("failed to place cell '%s' of type '%s'\n", cell->name.c_str(ctx), cell->type.c_str(ctx)); - } - --iters; - ctx->unbindBel(ripup_target->bel); - best_bel = ripup_bel; - } else { - ripup_target = nullptr; - all_placed = true; - } - ctx->bindBel(best_bel, cell, STRENGTH_WEAK); - if (require_legality && !ctx->isBelLocationValid(best_bel)) { - ctx->unbindBel(best_bel); - if (ripup_target != nullptr) { - ctx->bindBel(best_bel, ripup_target, STRENGTH_WEAK); - } - all_placed = false; - continue; - } - if (ctx->verbose) - log_info(" placed single cell '%s' at '%s'\n", cell->name.c_str(ctx), ctx->nameOfBel(best_bel)); - cell = ripup_target; - } - return true; -} - -class ConstraintLegaliseWorker -{ - private: - Context *ctx; - std::set rippedCells; - dict oldLocations; - dict> cluster2cells; - - class IncreasingDiameterSearch - { - public: - IncreasingDiameterSearch() : start(0), min(0), max(-1){}; - IncreasingDiameterSearch(int x) : start(x), min(x), max(x){}; - IncreasingDiameterSearch(int start, int min, int max) : start(start), min(min), max(max){}; - bool done() const { return (diameter > (max - min)); }; - int get() const - { - int val = start + sign * diameter; - val = std::max(val, min); - val = std::min(val, max); - return val; - } - - void next() - { - if (sign == 0) { - sign = 1; - diameter = 1; - } else if (sign == -1) { - sign = 1; - if ((start + sign * diameter) > max) - sign = -1; - ++diameter; - } else { - sign = -1; - if ((start + sign * diameter) < min) { - sign = 1; - ++diameter; - } - } - } - - void reset() - { - sign = 0; - diameter = 0; - } - - private: - int start, min, max; - int diameter = 0; - int sign = 0; - }; - - typedef dict CellLocations; - - // Check if a location would be suitable for a cell and all its constrained children - bool valid_loc_for(const CellInfo *cell, Loc loc, CellLocations &solution, pool &usedLocations) - { - BelId locBel = ctx->getBelByLocation(loc); - if (locBel == BelId()) - return false; - - if (cell->cluster == ClusterId()) { - if (!ctx->isValidBelForCellType(cell->type, locBel)) - return false; - if (!ctx->checkBelAvail(locBel)) { - CellInfo *confCell = ctx->getConflictingBelCell(locBel); - if (confCell->belStrength >= STRENGTH_STRONG) { - return false; - } - } - // Don't place at tiles where any strongly bound Bels exist, as we might need to rip them up later - for (auto tilebel : ctx->getBelsByTile(loc.x, loc.y)) { - CellInfo *tcell = ctx->getBoundBelCell(tilebel); - if (tcell && tcell->belStrength >= STRENGTH_STRONG) - return false; - } - usedLocations.insert(loc); - solution[cell->name] = loc; - } else { - std::vector> placement; - if (!ctx->getClusterPlacement(cell->cluster, locBel, placement)) - return false; - for (auto &p : placement) { - Loc p_loc = ctx->getBelLocation(p.second); - if (!ctx->checkBelAvail(p.second)) { - CellInfo *confCell = ctx->getConflictingBelCell(p.second); - if (confCell->belStrength >= STRENGTH_STRONG) { - return false; - } - } - // Don't place at tiles where any strongly bound Bels exist, as we might need to rip them up later - for (auto tilebel : ctx->getBelsByTile(p_loc.x, p_loc.y)) { - CellInfo *tcell = ctx->getBoundBelCell(tilebel); - if (tcell && tcell->belStrength >= STRENGTH_STRONG) - return false; - } - usedLocations.insert(p_loc); - solution[p.first->name] = p_loc; - } - } - - return true; - } - - // Set the strength to locked on all cells in chain - void lockdown_chain(CellInfo *root) - { - root->belStrength = STRENGTH_STRONG; - if (root->cluster != ClusterId()) - for (auto child : cluster2cells.at(root->cluster)) - child->belStrength = STRENGTH_STRONG; - } - - // Legalise placement constraints on a cell - bool legalise_cell(CellInfo *cell) - { - if (cell->cluster != ClusterId() && ctx->getClusterRootCell(cell->cluster) != cell) - return true; // Only process chain roots - if (constraints_satisfied(cell)) { - if (cell->cluster != ClusterId()) - lockdown_chain(cell); - } else { - IncreasingDiameterSearch xRootSearch, yRootSearch, zRootSearch; - Loc currentLoc; - if (cell->bel != BelId()) - currentLoc = ctx->getBelLocation(cell->bel); - else - currentLoc = oldLocations[cell->name]; - xRootSearch = IncreasingDiameterSearch(currentLoc.x, 0, ctx->getGridDimX() - 1); - yRootSearch = IncreasingDiameterSearch(currentLoc.y, 0, ctx->getGridDimY() - 1); - zRootSearch = IncreasingDiameterSearch(currentLoc.z, 0, ctx->getTileBelDimZ(currentLoc.x, currentLoc.y)); - - while (!xRootSearch.done()) { - Loc rootLoc; - - rootLoc.x = xRootSearch.get(); - rootLoc.y = yRootSearch.get(); - rootLoc.z = zRootSearch.get(); - zRootSearch.next(); - if (zRootSearch.done()) { - zRootSearch.reset(); - yRootSearch.next(); - if (yRootSearch.done()) { - yRootSearch.reset(); - xRootSearch.next(); - } - } - - CellLocations solution; - pool used; - if (valid_loc_for(cell, rootLoc, solution, used)) { - for (auto cp : solution) { - // First unbind all cells - if (ctx->cells.at(cp.first)->bel != BelId()) - ctx->unbindBel(ctx->cells.at(cp.first)->bel); - } - for (auto cp : solution) { - if (ctx->verbose) - log_info(" placing '%s' at (%d, %d, %d)\n", cp.first.c_str(ctx), cp.second.x, - cp.second.y, cp.second.z); - BelId target = ctx->getBelByLocation(cp.second); - if (!ctx->checkBelAvail(target)) { - CellInfo *confl_cell = ctx->getConflictingBelCell(target); - if (confl_cell != nullptr) { - if (ctx->verbose) - log_info(" '%s' already placed at '%s'\n", ctx->nameOf(confl_cell), - ctx->nameOfBel(confl_cell->bel)); - NPNR_ASSERT(confl_cell->belStrength < STRENGTH_STRONG); - ctx->unbindBel(target); - rippedCells.insert(confl_cell->name); - } - } - ctx->bindBel(target, ctx->cells.at(cp.first).get(), STRENGTH_STRONG); - rippedCells.erase(cp.first); - } - for (auto cp : solution) { - for (auto bel : ctx->getBelsByTile(cp.second.x, cp.second.y)) { - CellInfo *belCell = ctx->getBoundBelCell(bel); - if (belCell != nullptr && !solution.count(belCell->name)) { - if (!ctx->isBelLocationValid(bel)) { - NPNR_ASSERT(belCell->belStrength < STRENGTH_STRONG); - ctx->unbindBel(bel); - rippedCells.insert(belCell->name); - } - } - } - } - NPNR_ASSERT(constraints_satisfied(cell)); - return true; - } - } - return false; - } - return true; - } - - // Check if constraints are currently satisfied on a cell and its children - bool constraints_satisfied(const CellInfo *cell) { return get_constraints_distance(ctx, cell) == 0; } - - public: - ConstraintLegaliseWorker(Context *ctx) : ctx(ctx) - { - for (auto &cell : ctx->cells) { - if (cell.second->cluster != ClusterId()) - cluster2cells[cell.second->cluster].push_back(cell.second.get()); - } - }; - - unsigned print_stats(const char *point) - { - float distance_sum = 0; - float max_distance = 0; - unsigned moved_cells = 0; - unsigned unplaced_cells = 0; - for (auto orig : oldLocations) { - if (ctx->cells.at(orig.first)->bel == BelId()) { - unplaced_cells++; - continue; - } - Loc newLoc = ctx->getBelLocation(ctx->cells.at(orig.first)->bel); - if (newLoc != orig.second) { - float distance = std::sqrt(std::pow(newLoc.x - orig.second.x, 2) + pow(newLoc.y - orig.second.y, 2)); - moved_cells++; - distance_sum += distance; - if (distance > max_distance) - max_distance = distance; - } - } - log_info(" moved %d cells, %d unplaced (after %s)\n", moved_cells, unplaced_cells, point); - if (moved_cells > 0) { - log_info(" average distance %f\n", (distance_sum / moved_cells)); - log_info(" maximum distance %f\n", max_distance); - } - return moved_cells + unplaced_cells; - } - - int legalise_constraints() - { - log_info("Legalising relative constraints...\n"); - for (auto &cell : ctx->cells) { - oldLocations[cell.first] = ctx->getBelLocation(cell.second->bel); - } - for (auto &cell : ctx->cells) { - bool res = legalise_cell(cell.second.get()); - if (!res) { - log_error("failed to place chain starting at cell '%s'\n", cell.first.c_str(ctx)); - return -1; - } - } - if (print_stats("legalising chains") == 0) - return 0; - for (auto rippedCell : rippedCells) { - bool res = place_single_cell(ctx, ctx->cells.at(rippedCell).get(), true); - if (!res) { - log_error("failed to place cell '%s' after relative constraint legalisation\n", rippedCell.c_str(ctx)); - return -1; - } - } - auto score = print_stats("replacing ripped up cells"); - for (auto &cell : ctx->cells) - if (get_constraints_distance(ctx, cell.second.get()) != 0) - log_error("constraint satisfaction check failed for cell '%s' at Bel '%s'\n", cell.first.c_str(ctx), - ctx->nameOfBel(cell.second->bel)); - return score; - } -}; - -bool legalise_relative_constraints(Context *ctx) { return ConstraintLegaliseWorker(ctx).legalise_constraints() > 0; } - -// Get the total distance from satisfied constraints for a cell -int get_constraints_distance(const Context *ctx, const CellInfo *cell) -{ - int dist = 0; - if (cell->bel == BelId()) - return 100000; - Loc loc = ctx->getBelLocation(cell->bel); - - if (cell->cluster != ClusterId()) { - CellInfo *root = ctx->getClusterRootCell(cell->cluster); - if (root == cell) { - // parent - std::vector> placement; - if (!ctx->getClusterPlacement(cell->cluster, cell->bel, placement)) { - return 100000; - } else { - for (const auto &p : placement) { - if (p.first->bel == BelId()) - return 100000; - Loc c_loc = ctx->getBelLocation(p.first->bel); - Loc p_loc = ctx->getBelLocation(p.second); - dist += std::abs(c_loc.x - p_loc.x); - dist += std::abs(c_loc.y - p_loc.y); - dist += std::abs(c_loc.z - p_loc.z); - } - } - } else { - // child - if (root->bel == BelId()) - return 100000; - Loc root_loc = ctx->getBelLocation(root->bel); - Loc offset = ctx->getClusterOffset(cell); - dist += std::abs((root_loc.x + offset.x) - loc.x); - dist += std::abs((root_loc.y + offset.y) - loc.y); - } - } - - return dist; -} - -NEXTPNR_NAMESPACE_END diff --git a/common/place_common.h b/common/place_common.h deleted file mode 100644 index 5e5cbee3..00000000 --- a/common/place_common.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * 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 PLACE_COMMON_H -#define PLACE_COMMON_H - -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -typedef int64_t wirelen_t; - -enum class MetricType -{ - COST, - WIRELENGTH -}; - -// Return the wirelength of a net -wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type, float &tns); - -// Return the wirelength of all nets connected to a cell -wirelen_t get_cell_metric(const Context *ctx, const CellInfo *cell, MetricType type); - -// Return the wirelength of all nets connected to a cell, when the cell is at a given bel -wirelen_t get_cell_metric_at_bel(const Context *ctx, CellInfo *cell, BelId bel, MetricType type); - -// Place a single cell in the lowest wirelength Bel available, optionally requiring validity check -bool place_single_cell(Context *ctx, CellInfo *cell, bool require_legality); - -// Modify a design s.t. all relative placement constraints are satisfied -bool legalise_relative_constraints(Context *ctx); - -// Get the total distance from satisfied constraints for a cell -int get_constraints_distance(const Context *ctx, const CellInfo *cell); - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/placer1.cc b/common/placer1.cc deleted file mode 100644 index a6ba3895..00000000 --- a/common/placer1.cc +++ /dev/null @@ -1,1317 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 gatecat - * - * Simulated annealing implementation based on arachne-pnr - * Copyright (C) 2015-2018 Cotton Seed - * - * 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 "placer1.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "fast_bels.h" -#include "log.h" -#include "place_common.h" -#include "scope_lock.h" -#include "timing.h" -#include "util.h" - -NEXTPNR_NAMESPACE_BEGIN - -class SAPlacer -{ - private: - struct BoundingBox - { - // Actual bounding box - int x0 = 0, x1 = 0, y0 = 0, y1 = 0; - // Number of cells at each extremity - int nx0 = 0, nx1 = 0, ny0 = 0, ny1 = 0; - wirelen_t hpwl(const Placer1Cfg &cfg) const - { - return wirelen_t(cfg.hpwl_scale_x * (x1 - x0) + cfg.hpwl_scale_y * (y1 - y0)); - } - }; - - public: - SAPlacer(Context *ctx, Placer1Cfg cfg) - : ctx(ctx), fast_bels(ctx, /*check_bel_available=*/false, cfg.minBelsForGridPick), cfg(cfg), tmg(ctx) - { - for (auto bel : ctx->getBels()) { - Loc loc = ctx->getBelLocation(bel); - max_x = std::max(max_x, loc.x); - max_y = std::max(max_y, loc.y); - } - diameter = std::max(max_x, max_y) + 1; - - pool cell_types_in_use; - for (auto &cell : ctx->cells) { - IdString cell_type = cell.second->type; - cell_types_in_use.insert(cell_type); - } - - for (auto cell_type : cell_types_in_use) { - fast_bels.addCellType(cell_type); - } - - net_bounds.resize(ctx->nets.size()); - net_arc_tcost.resize(ctx->nets.size()); - old_udata.reserve(ctx->nets.size()); - net_by_udata.reserve(ctx->nets.size()); - decltype(NetInfo::udata) n = 0; - for (auto &net : ctx->nets) { - old_udata.emplace_back(net.second->udata); - net_arc_tcost.at(n).resize(net.second->users.capacity()); - net.second->udata = n++; - net_by_udata.push_back(net.second.get()); - } - for (auto ®ion : ctx->region) { - Region *r = region.second.get(); - BoundingBox bb; - if (r->constr_bels) { - bb.x0 = std::numeric_limits::max(); - bb.x1 = std::numeric_limits::min(); - bb.y0 = std::numeric_limits::max(); - bb.y1 = std::numeric_limits::min(); - for (auto bel : r->bels) { - Loc loc = ctx->getBelLocation(bel); - bb.x0 = std::min(bb.x0, loc.x); - bb.x1 = std::max(bb.x1, loc.x); - bb.y0 = std::min(bb.y0, loc.y); - bb.y1 = std::max(bb.y1, loc.y); - } - } else { - bb.x0 = 0; - bb.y0 = 0; - bb.x1 = max_x; - bb.y1 = max_y; - } - region_bounds[r->name] = bb; - } - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (ci->cluster == ClusterId()) - continue; - cluster2cell[ci->cluster].push_back(ci); - } - } - - ~SAPlacer() - { - for (auto &net : ctx->nets) - net.second->udata = old_udata[net.second->udata]; - } - - bool place(bool refine = false) - { - log_break(); - - ScopeLock lock(ctx); - - size_t placed_cells = 0; - std::vector autoplaced; - std::vector chain_basis; - if (!refine) { - // Initial constraints placer - for (auto &cell_entry : ctx->cells) { - CellInfo *cell = cell_entry.second.get(); - auto loc = cell->attrs.find(ctx->id("BEL")); - if (loc != cell->attrs.end()) { - std::string loc_name = loc->second.as_string(); - BelId bel = ctx->getBelByNameStr(loc_name); - if (bel == BelId()) { - log_error("No Bel named \'%s\' located for " - "this chip (processing BEL attribute on \'%s\')\n", - loc_name.c_str(), cell->name.c_str(ctx)); - } - - if (!ctx->isValidBelForCellType(cell->type, bel)) { - IdString bel_type = ctx->getBelType(bel); - log_error("Bel \'%s\' of type \'%s\' does not match cell " - "\'%s\' of type \'%s\'\n", - loc_name.c_str(), bel_type.c_str(ctx), cell->name.c_str(ctx), cell->type.c_str(ctx)); - } - auto bound_cell = ctx->getBoundBelCell(bel); - if (bound_cell) { - log_error( - "Cell \'%s\' cannot be bound to bel \'%s\' since it is already bound to cell \'%s\'\n", - cell->name.c_str(ctx), loc_name.c_str(), bound_cell->name.c_str(ctx)); - } - - ctx->bindBel(bel, cell, STRENGTH_USER); - if (!ctx->isBelLocationValid(bel)) { - IdString bel_type = ctx->getBelType(bel); - log_error("Bel \'%s\' of type \'%s\' is not valid for cell " - "\'%s\' of type \'%s\'\n", - loc_name.c_str(), bel_type.c_str(ctx), cell->name.c_str(ctx), cell->type.c_str(ctx)); - } - locked_bels.insert(bel); - placed_cells++; - } - } - int constr_placed_cells = placed_cells; - log_info("Placed %d cells based on constraints.\n", int(placed_cells)); - ctx->yield(); - - // Sort to-place cells for deterministic initial placement - - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (ci->bel == BelId()) { - autoplaced.push_back(cell.second.get()); - } - } - std::sort(autoplaced.begin(), autoplaced.end(), [](CellInfo *a, CellInfo *b) { return a->name < b->name; }); - ctx->shuffle(autoplaced); - auto iplace_start = std::chrono::high_resolution_clock::now(); - // Place cells randomly initially - log_info("Creating initial placement for remaining %d cells.\n", int(autoplaced.size())); - - for (auto cell : autoplaced) { - place_initial(cell); - placed_cells++; - if ((placed_cells - constr_placed_cells) % 500 == 0) - log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), - int(autoplaced.size())); - } - if ((placed_cells - constr_placed_cells) % 500 != 0) - log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), - int(autoplaced.size())); - if (cfg.budgetBased && cfg.slack_redist_iter > 0) - assign_budget(ctx); - ctx->yield(); - auto iplace_end = std::chrono::high_resolution_clock::now(); - log_info("Initial placement time %.02fs\n", - std::chrono::duration(iplace_end - iplace_start).count()); - log_info("Running simulated annealing placer.\n"); - } else { - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (ci->belStrength > STRENGTH_STRONG) { - continue; - } else if (ci->cluster != ClusterId()) { - if (ctx->getClusterRootCell(ci->cluster) == ci) - chain_basis.push_back(ci); - else - continue; - } else { - autoplaced.push_back(ci); - } - } - require_legal = false; - diameter = 3; - log_info("Running simulated annealing placer for refinement.\n"); - } - auto saplace_start = std::chrono::high_resolution_clock::now(); - - // Invoke timing analysis to obtain criticalities - tmg.setup_only = true; - if (!cfg.budgetBased) - tmg.setup(); - - // Calculate costs after initial placement - setup_costs(); - moveChange.init(this); - curr_wirelen_cost = total_wirelen_cost(); - curr_timing_cost = total_timing_cost(); - last_wirelen_cost = curr_wirelen_cost; - last_timing_cost = curr_timing_cost; - - if (cfg.netShareWeight > 0) - setup_nets_by_tile(); - - wirelen_t avg_wirelen = curr_wirelen_cost; - wirelen_t min_wirelen = curr_wirelen_cost; - - int n_no_progress = 0; - temp = refine ? 1e-7 : cfg.startTemp; - - // Main simulated annealing loop - for (int iter = 1;; iter++) { - n_move = n_accept = 0; - improved = false; - - if (iter % 5 == 0 || iter == 1) - log_info(" at iteration #%d: temp = %f, timing cost = " - "%.0f, wirelen = %.0f\n", - iter, temp, double(curr_timing_cost), double(curr_wirelen_cost)); - - for (int m = 0; m < 15; ++m) { - // Loop through all automatically placed cells - for (auto cell : autoplaced) { - // Find another random Bel for this cell - BelId try_bel = random_bel_for_cell(cell); - // If valid, try and swap to a new position and see if - // the new position is valid/worthwhile - if (try_bel != BelId() && try_bel != cell->bel) - try_swap_position(cell, try_bel); - } - // Also try swapping chains, if applicable - for (auto cb : chain_basis) { - Loc chain_base_loc = ctx->getBelLocation(cb->bel); - BelId try_base = random_bel_for_cell(cb, chain_base_loc.z); - if (try_base != BelId() && try_base != cb->bel) - try_swap_chain(cb, try_base); - } - } - - if (ctx->debug) { - // Verify correctness of incremental wirelen updates - for (size_t i = 0; i < net_bounds.size(); i++) { - auto net = net_by_udata[i]; - if (ignore_net(net)) - continue; - auto &incr = net_bounds.at(i), gold = get_net_bounds(net); - NPNR_ASSERT(incr.x0 == gold.x0); - NPNR_ASSERT(incr.x1 == gold.x1); - NPNR_ASSERT(incr.y0 == gold.y0); - NPNR_ASSERT(incr.y1 == gold.y1); - NPNR_ASSERT(incr.nx0 == gold.nx0); - NPNR_ASSERT(incr.nx1 == gold.nx1); - NPNR_ASSERT(incr.ny0 == gold.ny0); - NPNR_ASSERT(incr.ny1 == gold.ny1); - } - } - - if (curr_wirelen_cost < min_wirelen) { - min_wirelen = curr_wirelen_cost; - improved = true; - } - - // Heuristic to improve placement on the 8k - if (improved) - n_no_progress = 0; - else - n_no_progress++; - - if (temp <= 1e-7 && n_no_progress >= (refine ? 1 : 5)) { - log_info(" at iteration #%d: temp = %f, timing cost = " - "%.0f, wirelen = %.0f \n", - iter, temp, double(curr_timing_cost), double(curr_wirelen_cost)); - break; - } - - double Raccept = double(n_accept) / double(n_move); - - int M = std::max(max_x, max_y) + 1; - - if (ctx->verbose) - log("iter #%d: temp = %f, timing cost = " - "%.0f, wirelen = %.0f, dia = %d, Ra = %.02f \n", - iter, temp, double(curr_timing_cost), double(curr_wirelen_cost), diameter, Raccept); - - if (curr_wirelen_cost < 0.95 * avg_wirelen && curr_wirelen_cost > 0) { - avg_wirelen = 0.8 * avg_wirelen + 0.2 * curr_wirelen_cost; - } else { - double diam_next = diameter * (1.0 - 0.44 + Raccept); - diameter = std::max(1, std::min(M, int(diam_next + 0.5))); - if (Raccept > 0.96) { - temp *= 0.5; - } else if (Raccept > 0.8) { - temp *= 0.9; - } else if (Raccept > 0.15 && diameter > 1) { - temp *= 0.95; - } else { - temp *= 0.8; - } - } - // Once cooled below legalise threshold, run legalisation and start requiring - // legal moves only - if (diameter < legalise_dia && require_legal) { - if (legalise_relative_constraints(ctx)) { - // Only increase temperature if something was moved - autoplaced.clear(); - chain_basis.clear(); - for (auto &cell : ctx->cells) { - if (cell.second->belStrength <= STRENGTH_STRONG && cell.second->cluster != ClusterId() && - ctx->getClusterRootCell(cell.second->cluster) == cell.second.get()) - chain_basis.push_back(cell.second.get()); - else if (cell.second->belStrength < STRENGTH_STRONG) - autoplaced.push_back(cell.second.get()); - } - // temp = post_legalise_temp; - // diameter = std::min(M, diameter * post_legalise_dia_scale); - ctx->shuffle(autoplaced); - - // Legalisation is a big change so force a slack redistribution here - if (cfg.slack_redist_iter > 0 && cfg.budgetBased) - assign_budget(ctx, true /* quiet */); - } - require_legal = false; - } else if (cfg.budgetBased && cfg.slack_redist_iter > 0 && iter % cfg.slack_redist_iter == 0) { - assign_budget(ctx, true /* quiet */); - } - - // Invoke timing analysis to obtain criticalities - if (!cfg.budgetBased && cfg.timing_driven) - tmg.run(); - // Need to rebuild costs after criticalities change - setup_costs(); - // Reset incremental bounds - moveChange.reset(this); - moveChange.new_net_bounds = net_bounds; - - // Recalculate total metric entirely to avoid rounding errors - // accumulating over time - curr_wirelen_cost = total_wirelen_cost(); - curr_timing_cost = total_timing_cost(); - last_wirelen_cost = curr_wirelen_cost; - last_timing_cost = curr_timing_cost; - // Let the UI show visualization updates. - ctx->yield(); - } - - auto saplace_end = std::chrono::high_resolution_clock::now(); - log_info("SA placement time %.02fs\n", std::chrono::duration(saplace_end - saplace_start).count()); - - // Final post-placement validity check - ctx->yield(); - for (auto bel : ctx->getBels()) { - CellInfo *cell = ctx->getBoundBelCell(bel); - if (!ctx->isBelLocationValid(bel)) { - std::string cell_text = "no cell"; - if (cell != nullptr) - cell_text = std::string("cell '") + ctx->nameOf(cell) + "'"; - if (ctx->force) { - log_warning("post-placement validity check failed for Bel '%s' " - "(%s)\n", - ctx->nameOfBel(bel), cell_text.c_str()); - } else { - log_error("post-placement validity check failed for Bel '%s' " - "(%s)\n", - ctx->nameOfBel(bel), cell_text.c_str()); - } - } - } - timing_analysis(ctx); - - return true; - } - - private: - // Initial random placement - void place_initial(CellInfo *cell) - { - bool all_placed = false; - int iters = 25; - while (!all_placed) { - BelId best_bel = BelId(); - uint64_t best_score = std::numeric_limits::max(), - best_ripup_score = std::numeric_limits::max(); - CellInfo *ripup_target = nullptr; - BelId ripup_bel = BelId(); - if (cell->bel != BelId()) { - ctx->unbindBel(cell->bel); - } - IdString targetType = cell->type; - - auto proc_bel = [&](BelId bel) { - if (ctx->isValidBelForCellType(targetType, bel)) { - if (ctx->checkBelAvail(bel)) { - uint64_t score = ctx->rng64(); - if (score <= best_score) { - best_score = score; - best_bel = bel; - } - } else { - uint64_t score = ctx->rng64(); - CellInfo *bound_cell = ctx->getBoundBelCell(bel); - if (score <= best_ripup_score && bound_cell->belStrength < STRENGTH_STRONG) { - best_ripup_score = score; - ripup_target = bound_cell; - ripup_bel = bel; - } - } - } - }; - - if (cell->region != nullptr && cell->region->constr_bels) { - for (auto bel : cell->region->bels) { - proc_bel(bel); - } - } else { - for (auto bel : ctx->getBels()) { - proc_bel(bel); - } - } - - if (best_bel == BelId()) { - if (iters == 0 || ripup_bel == BelId()) - log_error("failed to place cell '%s' of type '%s'\n", cell->name.c_str(ctx), cell->type.c_str(ctx)); - --iters; - ctx->unbindBel(ripup_target->bel); - best_bel = ripup_bel; - } else { - ripup_target = nullptr; - all_placed = true; - } - ctx->bindBel(best_bel, cell, STRENGTH_WEAK); - - if (!ctx->isBelLocationValid(best_bel)) { - ctx->unbindBel(best_bel); - if (ripup_target != nullptr) { - ctx->bindBel(best_bel, ripup_target, STRENGTH_WEAK); - } - all_placed = false; - continue; - } - - // Back annotate location - cell->attrs[ctx->id("BEL")] = ctx->getBelName(cell->bel).str(ctx); - cell = ripup_target; - } - } - - // Attempt a SA position swap, return true on success or false on failure - bool try_swap_position(CellInfo *cell, BelId newBel) - { - static const double epsilon = 1e-20; - moveChange.reset(this); - if (!require_legal && cell->cluster != ClusterId()) - return false; - BelId oldBel = cell->bel; - CellInfo *other_cell = ctx->getBoundBelCell(newBel); - if (!require_legal && other_cell != nullptr && - (other_cell->cluster != ClusterId() || other_cell->belStrength > STRENGTH_WEAK)) { - return false; - } - int old_dist = get_constraints_distance(ctx, cell); - int new_dist; - if (other_cell != nullptr) - old_dist += get_constraints_distance(ctx, other_cell); - double delta = 0; - - if (!ctx->isValidBelForCellType(cell->type, newBel)) { - return false; - } - if (other_cell != nullptr && !ctx->isValidBelForCellType(other_cell->type, oldBel)) { - return false; - } - - int net_delta_score = 0; - if (cfg.netShareWeight > 0) - net_delta_score += update_nets_by_tile(cell, ctx->getBelLocation(cell->bel), ctx->getBelLocation(newBel)); - - ctx->unbindBel(oldBel); - if (other_cell != nullptr) { - ctx->unbindBel(newBel); - } - - ctx->bindBel(newBel, cell, STRENGTH_WEAK); - - if (other_cell != nullptr) { - ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK); - if (cfg.netShareWeight > 0) - net_delta_score += - update_nets_by_tile(other_cell, ctx->getBelLocation(newBel), ctx->getBelLocation(oldBel)); - } - - add_move_cell(moveChange, cell, oldBel); - - if (other_cell != nullptr) { - add_move_cell(moveChange, other_cell, newBel); - } - - // Always check both the new and old locations; as in some cases of dedicated routing ripping up a cell can deny - // use of a dedicated path and thus make a site illegal - if (!ctx->isBelLocationValid(newBel) || !ctx->isBelLocationValid(oldBel)) { - ctx->unbindBel(newBel); - if (other_cell != nullptr) - ctx->unbindBel(oldBel); - goto swap_fail; - } - - // Recalculate metrics for all nets touched by the perturbation - compute_cost_changes(moveChange); - - new_dist = get_constraints_distance(ctx, cell); - if (other_cell != nullptr) - new_dist += get_constraints_distance(ctx, other_cell); - delta = lambda * (moveChange.timing_delta / std::max(last_timing_cost, epsilon)) + - (1 - lambda) * (double(moveChange.wirelen_delta) / std::max(last_wirelen_cost, epsilon)); - delta += (cfg.constraintWeight / temp) * (new_dist - old_dist) / last_wirelen_cost; - if (cfg.netShareWeight > 0) - delta += -cfg.netShareWeight * (net_delta_score / std::max(total_net_share, epsilon)); - n_move++; - // SA acceptance criteria - if (delta < 0 || (temp > 1e-8 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) { - n_accept++; - } else { - if (other_cell != nullptr) - ctx->unbindBel(oldBel); - ctx->unbindBel(newBel); - goto swap_fail; - } - commit_cost_changes(moveChange); -#if 0 - log_info("swap %s -> %s\n", cell->name.c_str(ctx), ctx->nameOfBel(newBel)); - if (other_cell != nullptr) - log_info("swap %s -> %s\n", other_cell->name.c_str(ctx), ctx->nameOfBel(oldBel)); -#endif - return true; - swap_fail: - ctx->bindBel(oldBel, cell, STRENGTH_WEAK); - if (other_cell != nullptr) { - ctx->bindBel(newBel, other_cell, STRENGTH_WEAK); - if (cfg.netShareWeight > 0) - update_nets_by_tile(other_cell, ctx->getBelLocation(oldBel), ctx->getBelLocation(newBel)); - } - if (cfg.netShareWeight > 0) - update_nets_by_tile(cell, ctx->getBelLocation(newBel), ctx->getBelLocation(oldBel)); - return false; - } - - // Swap the Bel of a cell with another, return the original location - BelId swap_cell_bels(CellInfo *cell, BelId newBel) - { - BelId oldBel = cell->bel; -#if 0 - log_info("%s old: %s new: %s\n", cell->name.c_str(ctx), ctx->nameOfBel(cell->bel), ctx->nameOfBel(newBel)); -#endif - CellInfo *bound = ctx->getBoundBelCell(newBel); - if (bound != nullptr) - ctx->unbindBel(newBel); - ctx->unbindBel(oldBel); - ctx->bindBel(newBel, cell, (cell->cluster != ClusterId()) ? STRENGTH_STRONG : STRENGTH_WEAK); - if (bound != nullptr) { - ctx->bindBel(oldBel, bound, (bound->cluster != ClusterId()) ? STRENGTH_STRONG : STRENGTH_WEAK); - if (cfg.netShareWeight > 0) - update_nets_by_tile(bound, ctx->getBelLocation(newBel), ctx->getBelLocation(oldBel)); - } - if (cfg.netShareWeight > 0) - update_nets_by_tile(cell, ctx->getBelLocation(oldBel), ctx->getBelLocation(newBel)); - return oldBel; - } - - // Attempt to swap a chain with a non-chain - bool try_swap_chain(CellInfo *cell, BelId newBase) - { - std::vector> cell_rel; - dict moved_cells; - double delta = 0; - int orig_share_cost = total_net_share; - moveChange.reset(this); -#if CHAIN_DEBUG - log_info("finding cells for chain swap %s\n", cell->name.c_str(ctx)); -#endif - std::queue> displaced_clusters; - displaced_clusters.emplace(cell->cluster, newBase); - while (!displaced_clusters.empty()) { - std::vector> dest_bels; - auto cursor = displaced_clusters.front(); -#if CHAIN_DEBUG - log_info("%d Cluster %s\n", __LINE__, cursor.first.c_str(ctx)); -#endif - displaced_clusters.pop(); - if (!ctx->getClusterPlacement(cursor.first, cursor.second, dest_bels)) - goto swap_fail; - for (const auto &db : dest_bels) { - // Ensure the cluster is ripped up - if (db.first->bel != BelId()) { - moved_cells[db.first->name] = db.first->bel; -#if CHAIN_DEBUG - log_info("%d unbind %s\n", __LINE__, ctx->nameOfBel(db.first->bel)); -#endif - ctx->unbindBel(db.first->bel); - } - } - for (const auto &db : dest_bels) { - CellInfo *bound = ctx->getBoundBelCell(db.second); - BelId old_bel = moved_cells.at(db.first->name); - if (!ctx->checkBelAvail(old_bel) && bound != nullptr) { - // Simple swap no longer possible - goto swap_fail; - } - if (bound != nullptr) { - if (moved_cells.count(bound->name)) { - // Don't move a cell multiple times in the same go - goto swap_fail; - } else if (bound->belStrength > STRENGTH_STRONG) { - goto swap_fail; - } else if (bound->cluster != ClusterId()) { - // Displace the entire cluster - Loc old_loc = ctx->getBelLocation(old_bel); - Loc bound_loc = ctx->getBelLocation(bound->bel); - Loc root_loc = ctx->getBelLocation(ctx->getClusterRootCell(bound->cluster)->bel); - BelId new_root = ctx->getBelByLocation(Loc(old_loc.x + (root_loc.x - bound_loc.x), - old_loc.y + (root_loc.y - bound_loc.y), - old_loc.z + (root_loc.z - bound_loc.z))); - if (new_root == BelId()) - goto swap_fail; - for (auto cluster_cell : cluster2cell.at(bound->cluster)) { - moved_cells[cluster_cell->name] = cluster_cell->bel; -#if CHAIN_DEBUG - log_info("%d unbind %s\n", __LINE__, ctx->nameOfBel(cluster_cell->bel)); -#endif - ctx->unbindBel(cluster_cell->bel); - } - displaced_clusters.emplace(bound->cluster, new_root); - } else { - // Just a single cell to move - moved_cells[bound->name] = bound->bel; -#if CHAIN_DEBUG - log_info("%d unbind %s\n", __LINE__, ctx->nameOfBel(bound->bel)); - log_info("%d bind %s %s\n", __LINE__, ctx->nameOfBel(old_bel), ctx->nameOf(bound)); -#endif - ctx->unbindBel(bound->bel); - ctx->bindBel(old_bel, bound, STRENGTH_WEAK); - } - } else if (!ctx->checkBelAvail(db.second)) { - goto swap_fail; - } - // All those shenanigans should now mean the target bel is free to use -#if CHAIN_DEBUG - log_info("%d bind %s %s\n", __LINE__, ctx->nameOfBel(db.second), ctx->nameOf(db.first)); -#endif - ctx->bindBel(db.second, db.first, STRENGTH_WEAK); - } - } - - for (const auto &mm : moved_cells) { - CellInfo *cell = ctx->cells.at(mm.first).get(); - add_move_cell(moveChange, cell, moved_cells.at(cell->name)); - if (cfg.netShareWeight > 0) - update_nets_by_tile(cell, ctx->getBelLocation(moved_cells.at(cell->name)), - ctx->getBelLocation(cell->bel)); - if (!ctx->isBelLocationValid(cell->bel) || !cell->testRegion(cell->bel)) - goto swap_fail; - } -#if CHAIN_DEBUG - log_info("legal chain swap %s\n", cell->name.c_str(ctx)); -#endif - compute_cost_changes(moveChange); - delta = lambda * (moveChange.timing_delta / last_timing_cost) + - (1 - lambda) * (double(moveChange.wirelen_delta) / last_wirelen_cost); - if (cfg.netShareWeight > 0) { - delta += - cfg.netShareWeight * (orig_share_cost - total_net_share) / std::max(total_net_share, 1e-20); - } - n_move++; - // SA acceptance criteria - if (delta < 0 || (temp > 1e-8 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) { - n_accept++; -#if CHAIN_DEBUG - log_info("accepted chain swap %s\n", cell->name.c_str(ctx)); -#endif - } else { - goto swap_fail; - } - commit_cost_changes(moveChange); - return true; - swap_fail: -#if CHAIN_DEBUG - log_info("Swap failed\n"); -#endif - for (auto cell_pair : moved_cells) { - CellInfo *cell = ctx->cells.at(cell_pair.first).get(); - if (cell->bel != BelId()) { -#if CHAIN_DEBUG - log_info("%d unbind %s\n", __LINE__, ctx->nameOfBel(cell->bel)); -#endif - ctx->unbindBel(cell->bel); - } - } - for (auto cell_pair : moved_cells) { - CellInfo *cell = ctx->cells.at(cell_pair.first).get(); -#if CHAIN_DEBUG - log_info("%d bind %s %s\n", __LINE__, ctx->nameOfBel(cell_pair.second), cell->name.c_str(ctx)); -#endif - ctx->bindBel(cell_pair.second, cell, STRENGTH_WEAK); - } - return false; - } - - // Find a random Bel of the correct type for a cell, within the specified - // diameter - BelId random_bel_for_cell(CellInfo *cell, int force_z = -1) - { - IdString targetType = cell->type; - Loc curr_loc = ctx->getBelLocation(cell->bel); - int count = 0; - - int dx = diameter, dy = diameter; - if (cell->region != nullptr && cell->region->constr_bels) { - dx = std::min(cfg.hpwl_scale_x * diameter, - (region_bounds[cell->region->name].x1 - region_bounds[cell->region->name].x0) + 1); - dy = std::min(cfg.hpwl_scale_y * diameter, - (region_bounds[cell->region->name].y1 - region_bounds[cell->region->name].y0) + 1); - // Clamp location to within bounds - curr_loc.x = std::max(region_bounds[cell->region->name].x0, curr_loc.x); - curr_loc.x = std::min(region_bounds[cell->region->name].x1, curr_loc.x); - curr_loc.y = std::max(region_bounds[cell->region->name].y0, curr_loc.y); - curr_loc.y = std::min(region_bounds[cell->region->name].y1, curr_loc.y); - } - - FastBels::FastBelsData *bel_data; - auto type_cnt = fast_bels.getBelsForCellType(targetType, &bel_data); - - while (true) { - int nx = ctx->rng(2 * dx + 1) + std::max(curr_loc.x - dx, 0); - int ny = ctx->rng(2 * dy + 1) + std::max(curr_loc.y - dy, 0); - if (cfg.minBelsForGridPick >= 0 && type_cnt < cfg.minBelsForGridPick) - nx = ny = 0; - if (nx >= int(bel_data->size())) - continue; - if (ny >= int(bel_data->at(nx).size())) - continue; - const auto &fb = bel_data->at(nx).at(ny); - if (fb.size() == 0) - continue; - BelId bel = fb.at(ctx->rng(int(fb.size()))); - if (force_z != -1) { - Loc loc = ctx->getBelLocation(bel); - if (loc.z != force_z) - continue; - } - if (!cell->testRegion(bel)) - continue; - if (locked_bels.find(bel) != locked_bels.end()) - continue; - count++; - return bel; - } - } - - // Return true if a net is to be entirely ignored - inline bool ignore_net(NetInfo *net) - { - return net->driver.cell == nullptr || net->driver.cell->bel == BelId() || - ctx->getBelGlobalBuf(net->driver.cell->bel); - } - - // Get the bounding box for a net - inline BoundingBox get_net_bounds(NetInfo *net) - { - BoundingBox bb; - NPNR_ASSERT(net->driver.cell != nullptr); - Loc dloc = ctx->getBelLocation(net->driver.cell->bel); - bb.x0 = dloc.x; - bb.x1 = dloc.x; - bb.y0 = dloc.y; - bb.y1 = dloc.y; - bb.nx0 = 1; - bb.nx1 = 1; - bb.ny0 = 1; - bb.ny1 = 1; - for (auto user : net->users) { - if (user.cell->bel == BelId()) - continue; - Loc uloc = ctx->getBelLocation(user.cell->bel); - if (bb.x0 == uloc.x) - ++bb.nx0; - else if (uloc.x < bb.x0) { - bb.x0 = uloc.x; - bb.nx0 = 1; - } - if (bb.x1 == uloc.x) - ++bb.nx1; - else if (uloc.x > bb.x1) { - bb.x1 = uloc.x; - bb.nx1 = 1; - } - if (bb.y0 == uloc.y) - ++bb.ny0; - else if (uloc.y < bb.y0) { - bb.y0 = uloc.y; - bb.ny0 = 1; - } - if (bb.y1 == uloc.y) - ++bb.ny1; - else if (uloc.y > bb.y1) { - bb.y1 = uloc.y; - bb.ny1 = 1; - } - } - - return bb; - } - - // Get the timing cost for an arc of a net - inline double get_timing_cost(NetInfo *net, const PortRef &user) - { - int cc; - if (net->driver.cell == nullptr) - return 0; - if (ctx->getPortTimingClass(net->driver.cell, net->driver.port, cc) == TMG_IGNORE) - return 0; - if (cfg.budgetBased) { - double delay = ctx->getDelayNS(ctx->predictArcDelay(net, user)); - return std::min(10.0, std::exp(delay - ctx->getDelayNS(user.budget) / 10)); - } else { - float crit = tmg.get_criticality(CellPortKey(user)); - double delay = ctx->getDelayNS(ctx->predictArcDelay(net, user)); - return delay * std::pow(crit, crit_exp); - } - } - - // Set up the cost maps - void setup_costs() - { - for (auto &net : ctx->nets) { - NetInfo *ni = net.second.get(); - if (ignore_net(ni)) - continue; - net_bounds[ni->udata] = get_net_bounds(ni); - if (cfg.timing_driven && int(ni->users.entries()) < cfg.timingFanoutThresh) - for (auto usr : ni->users.enumerate()) - net_arc_tcost[ni->udata][usr.index.idx()] = get_timing_cost(ni, usr.value); - } - } - - // Get the total wiring cost for the design - wirelen_t total_wirelen_cost() - { - wirelen_t cost = 0; - for (const auto &net : net_bounds) - cost += net.hpwl(cfg); - return cost; - } - - // Get the total timing cost for the design - double total_timing_cost() - { - double cost = 0; - for (const auto &net : net_arc_tcost) { - for (auto arc_cost : net) { - cost += arc_cost; - } - } - return cost; - } - - // Cost-change-related data for a move - struct MoveChangeData - { - - enum BoundChangeType - { - NO_CHANGE, - CELL_MOVED_INWARDS, - CELL_MOVED_OUTWARDS, - FULL_RECOMPUTE - }; - - std::vector bounds_changed_nets_x, bounds_changed_nets_y; - std::vector>> changed_arcs; - - std::vector already_bounds_changed_x, already_bounds_changed_y; - std::vector> already_changed_arcs; - - std::vector new_net_bounds; - std::vector>, double>> new_arc_costs; - - wirelen_t wirelen_delta = 0; - double timing_delta = 0; - - void init(SAPlacer *p) - { - already_bounds_changed_x.resize(p->ctx->nets.size()); - already_bounds_changed_y.resize(p->ctx->nets.size()); - already_changed_arcs.resize(p->ctx->nets.size()); - for (auto &net : p->ctx->nets) { - already_changed_arcs.at(net.second->udata).resize(net.second->users.capacity()); - } - new_net_bounds = p->net_bounds; - } - - void reset(SAPlacer *p) - { - for (auto bc : bounds_changed_nets_x) { - new_net_bounds[bc] = p->net_bounds[bc]; - already_bounds_changed_x[bc] = NO_CHANGE; - } - for (auto bc : bounds_changed_nets_y) { - new_net_bounds[bc] = p->net_bounds[bc]; - already_bounds_changed_y[bc] = NO_CHANGE; - } - for (const auto &tc : changed_arcs) - already_changed_arcs[tc.first][tc.second.idx()] = false; - bounds_changed_nets_x.clear(); - bounds_changed_nets_y.clear(); - changed_arcs.clear(); - new_arc_costs.clear(); - wirelen_delta = 0; - timing_delta = 0; - } - - } moveChange; - - void add_move_cell(MoveChangeData &mc, CellInfo *cell, BelId old_bel) - { - Loc curr_loc = ctx->getBelLocation(cell->bel); - Loc old_loc = ctx->getBelLocation(old_bel); - // Check net bounds - for (const auto &port : cell->ports) { - NetInfo *pn = port.second.net; - if (pn == nullptr) - continue; - if (ignore_net(pn)) - continue; - BoundingBox &curr_bounds = mc.new_net_bounds[pn->udata]; - // Incremental bounding box updates - // Note that everything other than full updates are applied immediately rather than being queued, - // so further updates to the same net in the same move are dealt with correctly. - // If a full update is already queued, this can be considered a no-op - if (mc.already_bounds_changed_x[pn->udata] != MoveChangeData::FULL_RECOMPUTE) { - // Bounds x0 - if (curr_loc.x < curr_bounds.x0) { - // Further out than current bounds x0 - curr_bounds.x0 = curr_loc.x; - curr_bounds.nx0 = 1; - if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) { - // Checking already_bounds_changed_x ensures that each net is only added once - // to bounds_changed_nets, lest we add its HPWL change multiple times skewing the - // overall cost change - mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; - mc.bounds_changed_nets_x.push_back(pn->udata); - } - } else if (curr_loc.x == curr_bounds.x0 && old_loc.x > curr_bounds.x0) { - curr_bounds.nx0++; - if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) { - mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; - mc.bounds_changed_nets_x.push_back(pn->udata); - } - } else if (old_loc.x == curr_bounds.x0 && curr_loc.x > curr_bounds.x0) { - if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) - mc.bounds_changed_nets_x.push_back(pn->udata); - if (curr_bounds.nx0 == 1) { - mc.already_bounds_changed_x[pn->udata] = MoveChangeData::FULL_RECOMPUTE; - } else { - curr_bounds.nx0--; - if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) - mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_INWARDS; - } - } - - // Bounds x1 - if (curr_loc.x > curr_bounds.x1) { - // Further out than current bounds x1 - curr_bounds.x1 = curr_loc.x; - curr_bounds.nx1 = 1; - if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) { - // Checking already_bounds_changed_x ensures that each net is only added once - // to bounds_changed_nets, lest we add its HPWL change multiple times skewing the - // overall cost change - mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; - mc.bounds_changed_nets_x.push_back(pn->udata); - } - } else if (curr_loc.x == curr_bounds.x1 && old_loc.x < curr_bounds.x1) { - curr_bounds.nx1++; - if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) { - mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; - mc.bounds_changed_nets_x.push_back(pn->udata); - } - } else if (old_loc.x == curr_bounds.x1 && curr_loc.x < curr_bounds.x1) { - if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) - mc.bounds_changed_nets_x.push_back(pn->udata); - if (curr_bounds.nx1 == 1) { - mc.already_bounds_changed_x[pn->udata] = MoveChangeData::FULL_RECOMPUTE; - } else { - curr_bounds.nx1--; - if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) - mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_INWARDS; - } - } - } - if (mc.already_bounds_changed_y[pn->udata] != MoveChangeData::FULL_RECOMPUTE) { - // Bounds y0 - if (curr_loc.y < curr_bounds.y0) { - // Further out than current bounds y0 - curr_bounds.y0 = curr_loc.y; - curr_bounds.ny0 = 1; - if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) { - mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; - mc.bounds_changed_nets_y.push_back(pn->udata); - } - } else if (curr_loc.y == curr_bounds.y0 && old_loc.y > curr_bounds.y0) { - curr_bounds.ny0++; - if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) { - mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; - mc.bounds_changed_nets_y.push_back(pn->udata); - } - } else if (old_loc.y == curr_bounds.y0 && curr_loc.y > curr_bounds.y0) { - if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) - mc.bounds_changed_nets_y.push_back(pn->udata); - if (curr_bounds.ny0 == 1) { - mc.already_bounds_changed_y[pn->udata] = MoveChangeData::FULL_RECOMPUTE; - } else { - curr_bounds.ny0--; - if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) - mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_INWARDS; - } - } - - // Bounds y1 - if (curr_loc.y > curr_bounds.y1) { - // Further out than current bounds y1 - curr_bounds.y1 = curr_loc.y; - curr_bounds.ny1 = 1; - if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) { - mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; - mc.bounds_changed_nets_y.push_back(pn->udata); - } - } else if (curr_loc.y == curr_bounds.y1 && old_loc.y < curr_bounds.y1) { - curr_bounds.ny1++; - if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) { - mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; - mc.bounds_changed_nets_y.push_back(pn->udata); - } - } else if (old_loc.y == curr_bounds.y1 && curr_loc.y < curr_bounds.y1) { - if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) - mc.bounds_changed_nets_y.push_back(pn->udata); - if (curr_bounds.ny1 == 1) { - mc.already_bounds_changed_y[pn->udata] = MoveChangeData::FULL_RECOMPUTE; - } else { - curr_bounds.ny1--; - if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) - mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_INWARDS; - } - } - } - - if (cfg.timing_driven && int(pn->users.entries()) < cfg.timingFanoutThresh) { - // Output ports - all arcs change timing - if (port.second.type == PORT_OUT) { - int cc; - TimingPortClass cls = ctx->getPortTimingClass(cell, port.first, cc); - if (cls != TMG_IGNORE) - for (auto usr : pn->users.enumerate()) - if (!mc.already_changed_arcs[pn->udata][usr.index.idx()]) { - mc.changed_arcs.emplace_back(std::make_pair(pn->udata, usr.index)); - mc.already_changed_arcs[pn->udata][usr.index.idx()] = true; - } - } else if (port.second.type == PORT_IN) { - auto usr_idx = port.second.user_idx; - if (!mc.already_changed_arcs[pn->udata][usr_idx.idx()]) { - mc.changed_arcs.emplace_back(std::make_pair(pn->udata, usr_idx)); - mc.already_changed_arcs[pn->udata][usr_idx.idx()] = true; - } - } - } - } - } - - void compute_cost_changes(MoveChangeData &md) - { - for (const auto &bc : md.bounds_changed_nets_x) { - if (md.already_bounds_changed_x[bc] == MoveChangeData::FULL_RECOMPUTE) - md.new_net_bounds[bc] = get_net_bounds(net_by_udata[bc]); - } - for (const auto &bc : md.bounds_changed_nets_y) { - if (md.already_bounds_changed_x[bc] != MoveChangeData::FULL_RECOMPUTE && - md.already_bounds_changed_y[bc] == MoveChangeData::FULL_RECOMPUTE) - md.new_net_bounds[bc] = get_net_bounds(net_by_udata[bc]); - } - - for (const auto &bc : md.bounds_changed_nets_x) - md.wirelen_delta += md.new_net_bounds[bc].hpwl(cfg) - net_bounds[bc].hpwl(cfg); - for (const auto &bc : md.bounds_changed_nets_y) - if (md.already_bounds_changed_x[bc] == MoveChangeData::NO_CHANGE) - md.wirelen_delta += md.new_net_bounds[bc].hpwl(cfg) - net_bounds[bc].hpwl(cfg); - - if (cfg.timing_driven) { - for (const auto &tc : md.changed_arcs) { - double old_cost = net_arc_tcost.at(tc.first).at(tc.second.idx()); - double new_cost = - get_timing_cost(net_by_udata.at(tc.first), net_by_udata.at(tc.first)->users.at(tc.second)); - md.new_arc_costs.emplace_back(std::make_pair(tc, new_cost)); - md.timing_delta += (new_cost - old_cost); - md.already_changed_arcs[tc.first][tc.second.idx()] = false; - } - } - } - - void commit_cost_changes(MoveChangeData &md) - { - for (const auto &bc : md.bounds_changed_nets_x) - net_bounds[bc] = md.new_net_bounds[bc]; - for (const auto &bc : md.bounds_changed_nets_y) - net_bounds[bc] = md.new_net_bounds[bc]; - for (const auto &tc : md.new_arc_costs) - net_arc_tcost[tc.first.first].at(tc.first.second.idx()) = tc.second; - curr_wirelen_cost += md.wirelen_delta; - curr_timing_cost += md.timing_delta; - } - - // Simple routeability driven placement - const int large_cell_thresh = 50; - int total_net_share = 0; - std::vector>> nets_by_tile; - void setup_nets_by_tile() - { - total_net_share = 0; - nets_by_tile.resize(max_x + 1, std::vector>(max_y + 1)); - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (int(ci->ports.size()) > large_cell_thresh) - continue; - Loc loc = ctx->getBelLocation(ci->bel); - auto &nbt = nets_by_tile.at(loc.x).at(loc.y); - for (const auto &port : ci->ports) { - if (port.second.net == nullptr) - continue; - if (port.second.net->driver.cell == nullptr || ctx->getBelGlobalBuf(port.second.net->driver.cell->bel)) - continue; - int &s = nbt[port.second.net->name]; - if (s > 0) - ++total_net_share; - ++s; - } - } - } - - int update_nets_by_tile(CellInfo *ci, Loc old_loc, Loc new_loc) - { - if (int(ci->ports.size()) > large_cell_thresh) - return 0; - int loss = 0, gain = 0; - auto &nbt_old = nets_by_tile.at(old_loc.x).at(old_loc.y); - auto &nbt_new = nets_by_tile.at(new_loc.x).at(new_loc.y); - - for (const auto &port : ci->ports) { - if (port.second.net == nullptr) - continue; - if (port.second.net->driver.cell == nullptr || ctx->getBelGlobalBuf(port.second.net->driver.cell->bel)) - continue; - int &o = nbt_old[port.second.net->name]; - --o; - NPNR_ASSERT(o >= 0); - if (o > 0) - ++loss; - int &n = nbt_new[port.second.net->name]; - if (n > 0) - ++gain; - ++n; - } - int delta = gain - loss; - total_net_share += delta; - return delta; - } - - // Get the combined wirelen/timing metric - inline double curr_metric() - { - return lambda * curr_timing_cost + (1 - lambda) * curr_wirelen_cost - cfg.netShareWeight * total_net_share; - } - - // Map nets to their bounding box (so we can skip recompute for moves that do not exceed the bounds - std::vector net_bounds; - // Map net arcs to their timing cost (criticality * delay ns) - std::vector> net_arc_tcost; - - // Fast lookup for cell to clusters - dict> cluster2cell; - - // Wirelength and timing cost at last and current iteration - wirelen_t last_wirelen_cost, curr_wirelen_cost; - double last_timing_cost, curr_timing_cost; - - Context *ctx; - float temp = 10; - float crit_exp = 8; - float lambda = 0.5; - bool improved = false; - int n_move, n_accept; - int diameter = 35, max_x = 1, max_y = 1; - dict> bel_types; - dict region_bounds; - FastBels fast_bels; - pool locked_bels; - std::vector net_by_udata; - std::vector old_udata; - bool require_legal = true; - const int legalise_dia = 4; - Placer1Cfg cfg; - - TimingAnalyser tmg; -}; - -Placer1Cfg::Placer1Cfg(Context *ctx) -{ - constraintWeight = ctx->setting("placer1/constraintWeight", 10); - netShareWeight = ctx->setting("placer1/netShareWeight", 0); - minBelsForGridPick = ctx->setting("placer1/minBelsForGridPick", 64); - budgetBased = ctx->setting("placer1/budgetBased", false); - startTemp = ctx->setting("placer1/startTemp", 1); - timingFanoutThresh = std::numeric_limits::max(); - timing_driven = ctx->setting("timing_driven"); - slack_redist_iter = ctx->setting("slack_redist_iter"); - hpwl_scale_x = 1; - hpwl_scale_y = 1; -} - -bool placer1(Context *ctx, Placer1Cfg cfg) -{ - try { - SAPlacer placer(ctx, cfg); - placer.place(); - log_info("Checksum: 0x%08x\n", ctx->checksum()); -#ifndef NDEBUG - ctx->lock(); - ctx->check(); - ctx->unlock(); -#endif - return true; - } catch (log_execution_error_exception) { -#ifndef NDEBUG - ctx->lock(); - ctx->check(); - ctx->unlock(); -#endif - return false; - } -} - -bool placer1_refine(Context *ctx, Placer1Cfg cfg) -{ - try { - SAPlacer placer(ctx, cfg); - placer.place(true); - log_info("Checksum: 0x%08x\n", ctx->checksum()); -#ifndef NDEBUG - ctx->lock(); - ctx->check(); - ctx->unlock(); -#endif - return true; - } catch (log_execution_error_exception) { -#ifndef NDEBUG - ctx->lock(); - ctx->check(); - ctx->unlock(); -#endif - return false; - } -} - -NEXTPNR_NAMESPACE_END diff --git a/common/placer1.h b/common/placer1.h deleted file mode 100644 index 9dfb0b0d..00000000 --- a/common/placer1.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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 PLACE_H -#define PLACE_H - -#include "log.h" -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct Placer1Cfg -{ - Placer1Cfg(Context *ctx); - float constraintWeight, netShareWeight; - int minBelsForGridPick; - bool budgetBased; - float startTemp; - int timingFanoutThresh; - bool timing_driven; - int slack_redist_iter; - int hpwl_scale_x, hpwl_scale_y; -}; - -extern bool placer1(Context *ctx, Placer1Cfg cfg); -extern bool placer1_refine(Context *ctx, Placer1Cfg cfg); - -NEXTPNR_NAMESPACE_END - -#endif // PLACE_H diff --git a/common/placer_heap.cc b/common/placer_heap.cc deleted file mode 100644 index 4c9ffb23..00000000 --- a/common/placer_heap.cc +++ /dev/null @@ -1,1830 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2019 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. - * - * [[cite]] HeAP - * Analytical Placement for Heterogeneous FPGAs, Marcel Gort and Jason H. Anderson - * https://janders.eecg.utoronto.ca/pdfs/marcelfpl12.pdf - * - * [[cite]] SimPL - * SimPL: An Effective Placement Algorithm, Myung-Chul Kim, Dong-Jin Lee and Igor L. Markov - * http://www.ece.umich.edu/cse/awards/pdfs/iccad10-simpl.pdf - * - * Notable changes from the original algorithm - * - Following the other nextpnr placer, Bels are placed rather than CLBs. This means a strict legalisation pass is - * added in addition to coarse legalisation (referred to as "spreading" to avoid confusion with strict legalisation) - * as described in HeAP to ensure validity. This searches random bels in the vicinity of the position chosen by - * spreading, with diameter increasing over iterations, with a heuristic to prefer lower wirelength choices. - * - To make the placer timing-driven, the bound2bound weights are multiplied by (1 + 10 * crit^2) - */ - -#ifdef WITH_HEAP - -#include "placer_heap.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "fast_bels.h" -#include "log.h" -#include "nextpnr.h" -#include "parallel_refine.h" -#include "place_common.h" -#include "placer1.h" -#include "scope_lock.h" -#include "timing.h" -#include "util.h" - -NEXTPNR_NAMESPACE_BEGIN - -namespace { -// A simple internal representation for a sparse system of equations Ax = rhs -// This is designed to decouple the functions that build the matrix to the engine that -// solves it, and the representation that requires -template struct EquationSystem -{ - - EquationSystem(size_t rows, size_t cols) - { - A.resize(cols); - rhs.resize(rows); - } - - // Simple sparse format, easy to convert to CCS for solver - std::vector>> A; // col -> (row, x[row, col]) sorted by row - std::vector rhs; // RHS vector - void reset() - { - for (auto &col : A) - col.clear(); - std::fill(rhs.begin(), rhs.end(), T()); - } - - void add_coeff(int row, int col, T val) - { - auto &Ac = A.at(col); - // Binary search - int b = 0, e = int(Ac.size()) - 1; - while (b <= e) { - int i = (b + e) / 2; - if (Ac.at(i).first == row) { - Ac.at(i).second += val; - return; - } - if (Ac.at(i).first > row) - e = i - 1; - else - b = i + 1; - } - Ac.insert(Ac.begin() + b, std::make_pair(row, val)); - } - - void add_rhs(int row, T val) { rhs[row] += val; } - - void solve(std::vector &x, float tolerance) - { - using namespace Eigen; - if (x.empty()) - return; - NPNR_ASSERT(x.size() == A.size()); - - VectorXd vx(x.size()), vb(rhs.size()); - SparseMatrix mat(A.size(), A.size()); - - std::vector colnnz; - for (auto &Ac : A) - colnnz.push_back(int(Ac.size())); - mat.reserve(colnnz); - for (int col = 0; col < int(A.size()); col++) { - auto &Ac = A.at(col); - for (auto &el : Ac) - mat.insert(el.first, col) = el.second; - } - - for (int i = 0; i < int(x.size()); i++) - vx[i] = x.at(i); - for (int i = 0; i < int(rhs.size()); i++) - vb[i] = rhs.at(i); - - ConjugateGradient, Lower | Upper> solver; - solver.setTolerance(tolerance); - VectorXd xr = solver.compute(mat).solveWithGuess(vb, vx); - for (int i = 0; i < int(x.size()); i++) - x.at(i) = xr[i]; - // for (int i = 0; i < int(x.size()); i++) - // log_info("x[%d] = %f\n", i, x.at(i)); - } -}; - -} // namespace - -class HeAPPlacer -{ - public: - HeAPPlacer(Context *ctx, PlacerHeapCfg cfg) - : ctx(ctx), cfg(cfg), fast_bels(ctx, /*check_bel_available=*/true, -1), tmg(ctx) - { - Eigen::initParallel(); - tmg.setup_only = true; - tmg.setup(); - - for (auto &cell : ctx->cells) - if (cell.second->cluster != ClusterId()) - cluster2cells[cell.second->cluster].push_back(cell.second.get()); - } - - bool place() - { - auto startt = std::chrono::high_resolution_clock::now(); - - ScopeLock lock(ctx); - place_constraints(); - build_fast_bels(); - seed_placement(); - update_all_chains(); - wirelen_t hpwl = total_hpwl(); - log_info("Creating initial analytic placement for %d cells, random placement wirelen = %d.\n", - int(place_cells.size()), int(hpwl)); - for (int i = 0; i < 4; i++) { - setup_solve_cells(); - auto solve_startt = std::chrono::high_resolution_clock::now(); -#ifdef NPNR_DISABLE_THREADS - build_solve_direction(false, -1); - build_solve_direction(true, -1); -#else - boost::thread xaxis([&]() { build_solve_direction(false, -1); }); - build_solve_direction(true, -1); - xaxis.join(); -#endif - auto solve_endt = std::chrono::high_resolution_clock::now(); - solve_time += std::chrono::duration(solve_endt - solve_startt).count(); - - update_all_chains(); - - hpwl = total_hpwl(); - log_info(" at initial placer iter %d, wirelen = %d\n", i, int(hpwl)); - } - - wirelen_t solved_hpwl = 0, spread_hpwl = 0, legal_hpwl = 0, best_hpwl = std::numeric_limits::max(); - int iter = 0, stalled = 0; - - std::vector> solution; - - std::vector> heap_runs; - pool all_buckets; - dict bucket_count; - - for (auto cell : place_cells) { - BelBucketId bucket = ctx->getBelBucketForCellType(cell->type); - if (!all_buckets.count(bucket)) { - heap_runs.push_back(pool{bucket}); - all_buckets.insert(bucket); - } - bucket_count[bucket]++; - } - // If more than 98% of cells are one cell type, always solve all at once - // Otherwise, follow full HeAP strategy of rotate&all - for (auto &c : bucket_count) { - if (c.second >= 0.98 * int(place_cells.size())) { - heap_runs.clear(); - break; - } - } - - if (cfg.placeAllAtOnce) { - // Never want to deal with LUTs, FFs, MUXFxs separately, - // for now disable all single-cell-type runs and only have heterogeneous - // runs - heap_runs.clear(); - } - - heap_runs.push_back(all_buckets); - // The main HeAP placer loop - log_info("Running main analytical placer.\n"); - while (stalled < 5 && (solved_hpwl <= legal_hpwl * 0.8)) { - // Alternate between particular bel types and all bels - for (auto &run : heap_runs) { - auto run_startt = std::chrono::high_resolution_clock::now(); - - setup_solve_cells(&run); - if (solve_cells.empty()) - continue; - // Heuristic: don't bother with threading below a certain size - auto solve_startt = std::chrono::high_resolution_clock::now(); - - // Build the connectivity matrix and run the solver; multithreaded between x and y axes if applicable -#ifndef NPNR_DISABLE_THREADS - if (solve_cells.size() >= 500) { - boost::thread xaxis([&]() { build_solve_direction(false, (iter == 0) ? -1 : iter); }); - build_solve_direction(true, (iter == 0) ? -1 : iter); - xaxis.join(); - } else -#endif - { - build_solve_direction(false, (iter == 0) ? -1 : iter); - build_solve_direction(true, (iter == 0) ? -1 : iter); - } - auto solve_endt = std::chrono::high_resolution_clock::now(); - solve_time += std::chrono::duration(solve_endt - solve_startt).count(); - update_all_chains(); - solved_hpwl = total_hpwl(); - - update_all_chains(); - - // Run the spreader - for (const auto &group : cfg.cellGroups) - CutSpreader(this, group).run(); - - for (auto type : run) - if (std::all_of(cfg.cellGroups.begin(), cfg.cellGroups.end(), - [type](const pool &grp) { return !grp.count(type); })) - CutSpreader(this, {type}).run(); - - // Run strict legalisation to find a valid bel for all cells - update_all_chains(); - spread_hpwl = total_hpwl(); - legalise_placement_strict(true); - update_all_chains(); - - legal_hpwl = total_hpwl(); - auto run_stopt = std::chrono::high_resolution_clock::now(); - - IdString bucket_name = ctx->getBelBucketName(*run.begin()); - log_info(" at iteration #%d, type %s: wirelen solved = %d, spread = %d, legal = %d; time = %.02fs\n", - iter + 1, (run.size() > 1 ? "ALL" : bucket_name.c_str(ctx)), int(solved_hpwl), - int(spread_hpwl), int(legal_hpwl), - std::chrono::duration(run_stopt - run_startt).count()); - } - - // Update timing weights - if (cfg.timing_driven) - tmg.run(); - - if (legal_hpwl < best_hpwl) { - best_hpwl = legal_hpwl; - stalled = 0; - // Save solution - solution.clear(); - for (auto &cell : ctx->cells) { - solution.emplace_back(cell.second.get(), cell.second->bel, cell.second->belStrength); - } - } else { - ++stalled; - } - for (auto &cl : cell_locs) { - cl.second.legal_x = cl.second.x; - cl.second.legal_y = cl.second.y; - } - ctx->yield(); - ++iter; - } - - // Apply saved solution - for (auto &sc : solution) { - CellInfo *cell = std::get<0>(sc); - if (cell->bel != BelId()) - ctx->unbindBel(cell->bel); - } - for (auto &sc : solution) { - CellInfo *cell; - BelId bel; - PlaceStrength strength; - std::tie(cell, bel, strength) = sc; - ctx->bindBel(bel, cell, strength); - } - - for (auto &cell : ctx->cells) { - if (cell.second->bel == BelId()) - log_error("Found unbound cell %s\n", cell.first.c_str(ctx)); - if (ctx->getBoundBelCell(cell.second->bel) != cell.second.get()) - log_error("Found cell %s with mismatched binding\n", cell.first.c_str(ctx)); - if (ctx->debug) - log_info("AP soln: %s -> %s\n", cell.first.c_str(ctx), ctx->nameOfBel(cell.second->bel)); - } - - bool any_bad_placements = false; - for (auto bel : ctx->getBels()) { - CellInfo *cell = ctx->getBoundBelCell(bel); - if (!ctx->isBelLocationValid(bel)) { - std::string cell_text = "no cell"; - if (cell != nullptr) - cell_text = std::string("cell '") + ctx->nameOf(cell) + "'"; - log_warning("post-placement validity check failed for Bel '%s' " - "(%s)\n", - ctx->nameOfBel(bel), cell_text.c_str()); - any_bad_placements = true; - } - } - - if (any_bad_placements) { - return false; - } - - auto endtt = std::chrono::high_resolution_clock::now(); - log_info("HeAP Placer Time: %.02fs\n", std::chrono::duration(endtt - startt).count()); - log_info(" of which solving equations: %.02fs\n", solve_time); - log_info(" of which spreading cells: %.02fs\n", cl_time); - log_info(" of which strict legalisation: %.02fs\n", sl_time); - - ctx->check(); - lock.unlock_early(); - -#if !defined(__wasm) - if (cfg.parallelRefine) { - if (!parallel_refine(ctx, ParallelRefineCfg(ctx))) { - return false; - } - } else -#endif - { - if (!placer1_refine(ctx, Placer1Cfg(ctx))) { - return false; - } - } - - return true; - } - - private: - Context *ctx; - PlacerHeapCfg cfg; - - int max_x = 0, max_y = 0; - FastBels fast_bels; - dict> bel_types; - - TimingAnalyser tmg; - - struct BoundingBox - { - // Actual bounding box - int x0 = 0, x1 = 0, y0 = 0, y1 = 0; - }; - - dict constraint_region_bounds; - - // In some cases, we can't use bindBel because we allow overlap in the earlier stages. So we use this custom - // structure instead - struct CellLocation - { - int x, y; - int legal_x, legal_y; - double rawx, rawy; - bool locked, global; - }; - dict cell_locs; - // The set of cells that we will actually place. This excludes locked cells and children cells of macros/chains - // (only the root of each macro is placed.) - std::vector place_cells; - - // The cells in the current equation being solved (a subset of place_cells in some cases, where we only place - // cells of a certain type) - std::vector solve_cells; - - dict> cluster2cells; - dict chain_size; - // Performance counting - double solve_time = 0, cl_time = 0, sl_time = 0; - - // Place cells with the BEL attribute set to constrain them - void place_constraints() - { - size_t placed_cells = 0; - // Initial constraints placer - for (auto &cell_entry : ctx->cells) { - CellInfo *cell = cell_entry.second.get(); - - auto loc = cell->attrs.find(ctx->id("BEL")); - if (loc != cell->attrs.end()) { - std::string loc_name = loc->second.as_string(); - BelId bel = ctx->getBelByNameStr(loc_name); - if (bel == BelId()) { - log_error("No Bel named \'%s\' located for " - "this chip (processing BEL attribute on \'%s\')\n", - loc_name.c_str(), cell->name.c_str(ctx)); - } - - if (!ctx->isValidBelForCellType(cell->type, bel)) { - IdString bel_type = ctx->getBelType(bel); - log_error("Bel \'%s\' of type \'%s\' does not match cell " - "\'%s\' of type \'%s\'\n", - loc_name.c_str(), bel_type.c_str(ctx), cell->name.c_str(ctx), cell->type.c_str(ctx)); - } - auto bound_cell = ctx->getBoundBelCell(bel); - if (bound_cell) { - log_error("Cell \'%s\' cannot be bound to bel \'%s\' since it is already bound to cell \'%s\'\n", - cell->name.c_str(ctx), loc_name.c_str(), bound_cell->name.c_str(ctx)); - } - - ctx->bindBel(bel, cell, STRENGTH_USER); - if (!ctx->isBelLocationValid(bel)) { - IdString bel_type = ctx->getBelType(bel); - log_error("Bel \'%s\' of type \'%s\' is not valid for cell " - "\'%s\' of type \'%s\'\n", - loc_name.c_str(), bel_type.c_str(ctx), cell->name.c_str(ctx), cell->type.c_str(ctx)); - } - placed_cells++; - } - } - log_info("Placed %d cells based on constraints.\n", int(placed_cells)); - ctx->yield(); - } - - void build_fast_bels() - { - for (auto bel : ctx->getBels()) { - if (!ctx->checkBelAvail(bel)) - continue; - Loc loc = ctx->getBelLocation(bel); - max_x = std::max(max_x, loc.x); - max_y = std::max(max_y, loc.y); - } - - pool cell_types_in_use; - pool buckets_in_use; - for (auto &cell : ctx->cells) { - IdString cell_type = cell.second->type; - cell_types_in_use.insert(cell_type); - BelBucketId bucket = ctx->getBelBucketForCellType(cell_type); - buckets_in_use.insert(bucket); - } - - for (auto cell_type : cell_types_in_use) { - fast_bels.addCellType(cell_type); - } - for (auto bucket : buckets_in_use) { - fast_bels.addBelBucket(bucket); - } - - // Determine bounding boxes of region constraints - for (auto ®ion : ctx->region) { - Region *r = region.second.get(); - BoundingBox bb; - if (r->constr_bels) { - bb.x0 = std::numeric_limits::max(); - bb.x1 = std::numeric_limits::min(); - bb.y0 = std::numeric_limits::max(); - bb.y1 = std::numeric_limits::min(); - for (auto bel : r->bels) { - Loc loc = ctx->getBelLocation(bel); - bb.x0 = std::min(bb.x0, loc.x); - bb.x1 = std::max(bb.x1, loc.x); - bb.y0 = std::min(bb.y0, loc.y); - bb.y1 = std::max(bb.y1, loc.y); - } - } else { - bb.x0 = 0; - bb.y0 = 0; - bb.x1 = max_x; - bb.y1 = max_y; - } - constraint_region_bounds[r->name] = bb; - } - } - - // Build and solve in one direction - void build_solve_direction(bool yaxis, int iter) - { - for (int i = 0; i < 5; i++) { - EquationSystem esx(solve_cells.size(), solve_cells.size()); - build_equations(esx, yaxis, iter); - solve_equations(esx, yaxis); - } - } - - // Check if a cell has any meaningful connectivity - bool has_connectivity(CellInfo *cell) - { - for (auto port : cell->ports) { - if (port.second.net != nullptr && port.second.net->driver.cell != nullptr && - !port.second.net->users.empty()) - return true; - } - return false; - } - - // Build up a random initial placement, without regard to legality - // FIXME: Are there better approaches to the initial placement (e.g. greedy?) - void seed_placement() - { - pool cell_types; - for (const auto &cell : ctx->cells) { - cell_types.insert(cell.second->type); - } - - pool bels_used; - dict> available_bels; - - for (auto bel : ctx->getBels()) { - if (!ctx->checkBelAvail(bel)) { - continue; - } - - for (auto cell_type : cell_types) { - if (ctx->isValidBelForCellType(cell_type, bel)) { - available_bels[cell_type].push_back(bel); - } - } - } - - for (auto &t : available_bels) { - ctx->shuffle(t.second.begin(), t.second.end()); - } - - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (ci->bel != BelId()) { - Loc loc = ctx->getBelLocation(ci->bel); - cell_locs[cell.first].x = loc.x; - cell_locs[cell.first].y = loc.y; - cell_locs[cell.first].locked = true; - cell_locs[cell.first].global = ctx->getBelGlobalBuf(ci->bel); - } else if (ci->cluster == ClusterId() || ctx->getClusterRootCell(ci->cluster) == ci) { - bool placed = false; - int attempt_count = 0; - while (!placed) { - ++attempt_count; - if (attempt_count > 25000) { - log_error("Unable to find a placement location for cell '%s'\n", ci->name.c_str(ctx)); - } - - // Make sure this cell type is in the available BEL map at - // all. - if (!available_bels.count(ci->type)) { - log_error("Unable to place cell '%s', no BELs remaining to implement cell type '%s'\n", - ci->name.c_str(ctx), ci->type.c_str(ctx)); - } - - // Find an unused BEL from bels_for_cell_type. - auto &bels_for_cell_type = available_bels.at(ci->type); - BelId bel; - while (true) { - if (bels_for_cell_type.empty()) { - log_error("Unable to place cell '%s', no BELs remaining to implement cell type '%s'\n", - ci->name.c_str(ctx), ci->type.c_str(ctx)); - } - - BelId candidate_bel = bels_for_cell_type.back(); - bels_for_cell_type.pop_back(); - if (bels_used.count(candidate_bel)) { - // candidate_bel has already been used by another - // cell type, skip it. - continue; - } - - bel = candidate_bel; - break; - } - - Loc loc = ctx->getBelLocation(bel); - cell_locs[cell.first].x = loc.x; - cell_locs[cell.first].y = loc.y; - cell_locs[cell.first].locked = false; - cell_locs[cell.first].global = ctx->getBelGlobalBuf(bel); - - // FIXME - if (has_connectivity(cell.second.get()) && !cfg.ioBufTypes.count(ci->type)) { - bels_used.insert(bel); - place_cells.push_back(ci); - placed = true; - } else { - ctx->bindBel(bel, ci, STRENGTH_STRONG); - if (ctx->isBelLocationValid(bel)) { - cell_locs[cell.first].locked = true; - placed = true; - bels_used.insert(bel); - } else { - ctx->unbindBel(bel); - available_bels.at(ci->type).push_front(bel); - } - } - } - } - } - } - - // Setup the cells to be solved, returns the number of rows - int setup_solve_cells(pool *buckets = nullptr) - { - int row = 0; - solve_cells.clear(); - // First clear the udata of all cells - for (auto &cell : ctx->cells) - cell.second->udata = dont_solve; - // Then update cells to be placed, which excludes cell children - for (auto cell : place_cells) { - if (buckets && !buckets->count(ctx->getBelBucketForCellType(cell->type))) - continue; - cell->udata = row++; - solve_cells.push_back(cell); - } - // Finally, update the udata of children - for (auto &cluster : cluster2cells) - for (auto child : cluster.second) - child->udata = ctx->getClusterRootCell(cluster.first)->udata; - return row; - } - - // Update all chains - void update_all_chains() - { - for (auto cell : place_cells) { - chain_size[cell->name] = 1; - if (cell->cluster != ClusterId()) { - const auto base = cell_locs[cell->name]; - for (auto child : cluster2cells.at(cell->cluster)) { - if (child->type == cell->type && child != cell) - chain_size[cell->name]++; - Loc offset = ctx->getClusterOffset(child); - cell_locs[child->name].x = std::max(0, std::min(max_x, base.x + offset.x)); - cell_locs[child->name].y = std::max(0, std::min(max_y, base.y + offset.y)); - } - } - } - } - - // Run a function on all ports of a net - including the driver and all users - template void foreach_port(NetInfo *net, Tf func) - { - if (net->driver.cell != nullptr) - func(net->driver, store_index()); - for (auto usr : net->users.enumerate()) - func(usr.value, usr.index); - } - - // Build the system of equations for either X or Y - void build_equations(EquationSystem &es, bool yaxis, int iter = -1) - { - // Return the x or y position of a cell, depending on ydir - auto cell_pos = [&](CellInfo *cell) { return yaxis ? cell_locs.at(cell->name).y : cell_locs.at(cell->name).x; }; - auto legal_pos = [&](CellInfo *cell) { - return yaxis ? cell_locs.at(cell->name).legal_y : cell_locs.at(cell->name).legal_x; - }; - - es.reset(); - - for (auto &net : ctx->nets) { - NetInfo *ni = net.second.get(); - if (ni->driver.cell == nullptr) - continue; - if (ni->users.empty()) - continue; - if (cell_locs.at(ni->driver.cell->name).global) - continue; - // Find the bounds of the net in this axis, and the ports that correspond to these bounds - PortRef *lbport = nullptr, *ubport = nullptr; - int lbpos = std::numeric_limits::max(), ubpos = std::numeric_limits::min(); - foreach_port(ni, [&](PortRef &port, store_index user_idx) { - int pos = cell_pos(port.cell); - if (pos < lbpos) { - lbpos = pos; - lbport = &port; - } - if (pos > ubpos) { - ubpos = pos; - ubport = &port; - } - }); - NPNR_ASSERT(lbport != nullptr); - NPNR_ASSERT(ubport != nullptr); - - auto stamp_equation = [&](PortRef &var, PortRef &eqn, double weight) { - if (eqn.cell->udata == dont_solve) - return; - int row = eqn.cell->udata; - int v_pos = cell_pos(var.cell); - if (var.cell->udata != dont_solve) { - es.add_coeff(row, var.cell->udata, weight); - } else { - es.add_rhs(row, -v_pos * weight); - } - if (var.cell->cluster != ClusterId()) { - Loc offset = ctx->getClusterOffset(var.cell); - es.add_rhs(row, -(yaxis ? offset.y : offset.x) * weight); - } - }; - - // Add all relevant connections to the matrix - foreach_port(ni, [&](PortRef &port, store_index user_idx) { - int this_pos = cell_pos(port.cell); - auto process_arc = [&](PortRef *other) { - if (other == &port) - return; - int o_pos = cell_pos(other->cell); - double weight = 1.0 / (ni->users.entries() * - std::max(1, (yaxis ? cfg.hpwl_scale_y : cfg.hpwl_scale_x) * - std::abs(o_pos - this_pos))); - - if (user_idx) { - weight *= (1.0 + cfg.timingWeight * std::pow(tmg.get_criticality(CellPortKey(port)), - cfg.criticalityExponent)); - } - - // If cell 0 is not fixed, it will stamp +w on its equation and -w on the other end's equation, - // if the other end isn't fixed - stamp_equation(port, port, weight); - stamp_equation(port, *other, -weight); - stamp_equation(*other, *other, weight); - stamp_equation(*other, port, -weight); - }; - process_arc(lbport); - process_arc(ubport); - }); - } - if (iter != -1) { - float alpha = cfg.alpha; - for (size_t row = 0; row < solve_cells.size(); row++) { - int l_pos = legal_pos(solve_cells.at(row)); - int c_pos = cell_pos(solve_cells.at(row)); - - double weight = - alpha * iter / - std::max(1, (yaxis ? cfg.hpwl_scale_y : cfg.hpwl_scale_x) * std::abs(l_pos - c_pos)); - // Add an arc from legalised to current position - es.add_coeff(row, row, weight); - es.add_rhs(row, weight * l_pos); - } - } - } - - // Build the system of equations for either X or Y - void solve_equations(EquationSystem &es, bool yaxis) - { - // Return the x or y position of a cell, depending on ydir - auto cell_pos = [&](CellInfo *cell) { return yaxis ? cell_locs.at(cell->name).y : cell_locs.at(cell->name).x; }; - std::vector vals; - std::transform(solve_cells.begin(), solve_cells.end(), std::back_inserter(vals), cell_pos); - es.solve(vals, cfg.solverTolerance); - for (size_t i = 0; i < vals.size(); i++) - if (yaxis) { - cell_locs.at(solve_cells.at(i)->name).rawy = vals.at(i); - cell_locs.at(solve_cells.at(i)->name).y = std::min(max_y, std::max(0, int(vals.at(i)))); - if (solve_cells.at(i)->region != nullptr) - cell_locs.at(solve_cells.at(i)->name).y = - limit_to_reg(solve_cells.at(i)->region, cell_locs.at(solve_cells.at(i)->name).y, true); - } else { - cell_locs.at(solve_cells.at(i)->name).rawx = vals.at(i); - cell_locs.at(solve_cells.at(i)->name).x = std::min(max_x, std::max(0, int(vals.at(i)))); - if (solve_cells.at(i)->region != nullptr) - cell_locs.at(solve_cells.at(i)->name).x = - limit_to_reg(solve_cells.at(i)->region, cell_locs.at(solve_cells.at(i)->name).x, false); - } - } - - // Compute HPWL - wirelen_t total_hpwl() - { - wirelen_t hpwl = 0; - for (auto &net : ctx->nets) { - NetInfo *ni = net.second.get(); - if (ni->driver.cell == nullptr) - continue; - CellLocation &drvloc = cell_locs.at(ni->driver.cell->name); - if (drvloc.global) - continue; - int xmin = drvloc.x, xmax = drvloc.x, ymin = drvloc.y, ymax = drvloc.y; - for (auto &user : ni->users) { - CellLocation &usrloc = cell_locs.at(user.cell->name); - xmin = std::min(xmin, usrloc.x); - xmax = std::max(xmax, usrloc.x); - ymin = std::min(ymin, usrloc.y); - ymax = std::max(ymax, usrloc.y); - } - hpwl += cfg.hpwl_scale_x * (xmax - xmin) + cfg.hpwl_scale_y * (ymax - ymin); - } - return hpwl; - } - - // Strict placement legalisation, performed after the initial HeAP spreading - void legalise_placement_strict(bool require_validity = false) - { - auto startt = std::chrono::high_resolution_clock::now(); - - // Unbind all cells placed in this solution - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (ci->bel != BelId() && - (ci->udata != dont_solve || - (ci->cluster != ClusterId() && ctx->getClusterRootCell(ci->cluster)->udata != dont_solve))) - ctx->unbindBel(ci->bel); - } - - // At the moment we don't follow the full HeAP algorithm using cuts for legalisation, instead using - // the simple greedy largest-macro-first approach. - std::priority_queue> remaining; - for (auto cell : solve_cells) { - remaining.emplace(chain_size[cell->name], cell->name); - } - int ripup_radius = 2; - int total_iters = 0; - int total_iters_noreset = 0; - while (!remaining.empty()) { - auto top = remaining.top(); - remaining.pop(); - - CellInfo *ci = ctx->cells.at(top.second).get(); - // Was now placed, ignore - if (ci->bel != BelId()) - continue; - // log_info(" Legalising %s (%s)\n", top.second.c_str(ctx), ci->type.c_str(ctx)); - FastBels::FastBelsData *fb; - fast_bels.getBelsForCellType(ci->type, &fb); - int radius = 0; - int iter = 0; - int iter_at_radius = 0; - bool placed = false; - BelId bestBel; - int best_inp_len = std::numeric_limits::max(); - - total_iters++; - total_iters_noreset++; - if (total_iters > int(solve_cells.size())) { - total_iters = 0; - ripup_radius = std::max(std::max(max_x, max_y), ripup_radius * 2); - } - - if (total_iters_noreset > std::max(5000, 8 * int(ctx->cells.size()))) { - log_error("Unable to find legal placement for all cells, design is probably at utilisation limit.\n"); - } - - while (!placed) { - - // Set a conservative timeout - if (iter > std::max(10000, 3 * int(ctx->cells.size()))) - log_error("Unable to find legal placement for cell '%s', check constraints and utilisation.\n", - ctx->nameOf(ci)); - - // Determine a search radius around the solver location (which increases over time) that is clamped to - // the region constraint for the cell (if applicable) - int rx = radius, ry = radius; - - if (ci->region != nullptr) { - rx = std::min(radius, (constraint_region_bounds[ci->region->name].x1 - - constraint_region_bounds[ci->region->name].x0) / - 2 + - 1); - ry = std::min(radius, (constraint_region_bounds[ci->region->name].y1 - - constraint_region_bounds[ci->region->name].y0) / - 2 + - 1); - } - - // Pick a random X and Y location within our search radius - int nx = ctx->rng(2 * rx + 1) + std::max(cell_locs.at(ci->name).x - rx, 0); - int ny = ctx->rng(2 * ry + 1) + std::max(cell_locs.at(ci->name).y - ry, 0); - - iter++; - iter_at_radius++; - if (iter >= (10 * (radius + 1))) { - // No luck yet, increase radius - radius = std::min(std::max(max_x, max_y), radius + 1); - while (radius < std::max(max_x, max_y)) { - // Keep increasing the radius until it will actually increase the number of cells we are - // checking (e.g. BRAM and DSP will not be in all cols/rows), so we don't waste effort - for (int x = std::max(0, cell_locs.at(ci->name).x - radius); - x <= std::min(max_x, cell_locs.at(ci->name).x + radius); x++) { - if (x >= int(fb->size())) - break; - for (int y = std::max(0, cell_locs.at(ci->name).y - radius); - y <= std::min(max_y, cell_locs.at(ci->name).y + radius); y++) { - if (y >= int(fb->at(x).size())) - break; - if (fb->at(x).at(y).size() > 0) - goto notempty; - } - } - radius = std::min(std::max(max_x, max_y), radius + 1); - } - notempty: - iter_at_radius = 0; - iter = 0; - } - // If our randomly chosen cooridnate is out of bounds; or points to a tile with no relevant bels; ignore - // it - if (nx < 0 || nx > max_x) - continue; - if (ny < 0 || ny > max_y) - continue; - - if (nx >= int(fb->size())) - continue; - if (ny >= int(fb->at(nx).size())) - continue; - if (fb->at(nx).at(ny).empty()) - continue; - - // The number of attempts to find a location to try - int need_to_explore = 2 * radius; - - // If we have found at least one legal location; and made enough attempts; assume it's good enough and - // finish - if (iter_at_radius >= need_to_explore && bestBel != BelId()) { - CellInfo *bound = ctx->getBoundBelCell(bestBel); - if (bound != nullptr) { - ctx->unbindBel(bound->bel); - remaining.emplace(chain_size[bound->name], bound->name); - } - ctx->bindBel(bestBel, ci, STRENGTH_WEAK); - placed = true; - Loc loc = ctx->getBelLocation(bestBel); - cell_locs[ci->name].x = loc.x; - cell_locs[ci->name].y = loc.y; - break; - } - - if (ci->cluster == ClusterId()) { - // The case where we have no relative constraints - for (auto sz : fb->at(nx).at(ny)) { - // Look through all bels in this tile; checking region constraint if applicable - if (!ci->testRegion(sz)) - continue; - // Prefer available bels; unless we are dealing with a wide radius (e.g. difficult control sets) - // or occasionally trigger a tiebreaker - if (ctx->checkBelAvail(sz) || (radius > ripup_radius || ctx->rng(20000) < 10)) { - CellInfo *bound = ctx->getBoundBelCell(sz); - if (bound != nullptr) { - // Only rip up cells without constraints - if (bound->cluster != ClusterId()) - continue; - ctx->unbindBel(bound->bel); - } - // Provisionally bind the bel - ctx->bindBel(sz, ci, STRENGTH_WEAK); - if (require_validity && !ctx->isBelLocationValid(sz)) { - // New location is not legal; unbind the cell (and rebind the cell we ripped up if - // applicable) - ctx->unbindBel(sz); - if (bound != nullptr) - ctx->bindBel(sz, bound, STRENGTH_WEAK); - } else if (iter_at_radius < need_to_explore) { - // It's legal, but we haven't tried enough locations yet - ctx->unbindBel(sz); - if (bound != nullptr) - ctx->bindBel(sz, bound, STRENGTH_WEAK); - int input_len = 0; - // Compute a fast input wirelength metric at this bel; and save if better than our last - // try - for (auto &port : ci->ports) { - auto &p = port.second; - if (p.type != PORT_IN || p.net == nullptr || p.net->driver.cell == nullptr) - continue; - CellInfo *drv = p.net->driver.cell; - auto drv_loc = cell_locs.find(drv->name); - if (drv_loc == cell_locs.end()) - continue; - if (drv_loc->second.global) - continue; - input_len += std::abs(drv_loc->second.x - nx) + std::abs(drv_loc->second.y - ny); - } - if (input_len < best_inp_len) { - best_inp_len = input_len; - bestBel = sz; - } - break; - } else { - // It's legal, and we've tried enough. Finish. - if (bound != nullptr) - remaining.emplace(chain_size[bound->name], bound->name); - Loc loc = ctx->getBelLocation(sz); - cell_locs[ci->name].x = loc.x; - cell_locs[ci->name].y = loc.y; - placed = true; - break; - } - } - } - } else { - // We do have relative constraints - for (auto sz : fb->at(nx).at(ny)) { - // List of cells and their destination - std::vector> targets; - // List of bels we placed things at; and the cell that was there before if applicable - std::vector> swaps_made; - - if (!ctx->getClusterPlacement(ci->cluster, sz, targets)) - continue; - - for (auto &target : targets) { - // Check it satisfies the region constraint if applicable - if (!target.first->testRegion(target.second)) - goto fail; - CellInfo *bound = ctx->getBoundBelCell(target.second); - // Chains cannot overlap; so if we have to ripup a cell make sure it isn't part of a chain - if (bound != nullptr) - if (bound->cluster != ClusterId() || bound->belStrength > STRENGTH_WEAK) - goto fail; - } - // Actually perform the move; keeping track of the moves we make so we can revert them if needed - for (auto &target : targets) { - CellInfo *bound = ctx->getBoundBelCell(target.second); - if (bound != nullptr) - ctx->unbindBel(target.second); - ctx->bindBel(target.second, target.first, STRENGTH_STRONG); - swaps_made.emplace_back(target.second, bound); - } - // Check that the move we have made is legal - for (auto &sm : swaps_made) { - if (!ctx->isBelLocationValid(sm.first)) - goto fail; - } - - if (false) { - fail: - // If the move turned out to be illegal; revert all the moves we made - for (auto &swap : swaps_made) { - ctx->unbindBel(swap.first); - if (swap.second != nullptr) - ctx->bindBel(swap.first, swap.second, STRENGTH_WEAK); - } - continue; - } - for (auto &target : targets) { - Loc loc = ctx->getBelLocation(target.second); - cell_locs[target.first->name].x = loc.x; - cell_locs[target.first->name].y = loc.y; - // log_info("%s %d %d %d\n", target.first->name.c_str(ctx), loc.x, loc.y, loc.z); - } - for (auto &swap : swaps_made) { - // Where we have ripped up cells; add them to the queue - if (swap.second != nullptr) - remaining.emplace(chain_size[swap.second->name], swap.second->name); - } - - placed = true; - break; - } - } - } - } - auto endt = std::chrono::high_resolution_clock::now(); - sl_time += std::chrono::duration(endt - startt).count(); - } - // Implementation of the cut-based spreading as described in the HeAP/SimPL papers - - template T limit_to_reg(Region *reg, T val, bool dir) - { - if (reg == nullptr) - return val; - int limit_low = dir ? constraint_region_bounds[reg->name].y0 : constraint_region_bounds[reg->name].x0; - int limit_high = dir ? constraint_region_bounds[reg->name].y1 : constraint_region_bounds[reg->name].x1; - return std::max(std::min(val, limit_high), limit_low); - } - - struct ChainExtent - { - int x0, y0, x1, y1; - }; - - struct SpreaderRegion - { - int id; - int x0, y0, x1, y1; - std::vector cells, bels; - bool overused(float beta) const - { - for (size_t t = 0; t < cells.size(); t++) { - if (bels.at(t) < 4) { - if (cells.at(t) > bels.at(t)) - return true; - } else { - if (cells.at(t) > beta * bels.at(t)) - return true; - } - } - return false; - } - }; - - class CutSpreader - { - public: - CutSpreader(HeAPPlacer *p, const pool &buckets) : p(p), ctx(p->ctx), buckets(buckets) - { - // Get fast BELs data for all buckets being Cut/Spread. - size_t idx = 0; - for (BelBucketId bucket : buckets) { - type_index[bucket] = idx; - FastBels::FastBelsData *fast_bels; - p->fast_bels.getBelsForBelBucket(bucket, &fast_bels); - fb.push_back(fast_bels); - ++idx; - NPNR_ASSERT(fb.size() == idx); - } - } - static int seq; - void run() - { - auto startt = std::chrono::high_resolution_clock::now(); - init(); - find_overused_regions(); - for (auto &r : regions) { - if (merged_regions.count(r.id)) - continue; -#if 0 - log_info("%s (%d, %d) |_> (%d, %d) %d/%d\n", beltype.c_str(ctx), r.x0, r.y0, r.x1, r.y1, r.cells, - r.bels); -#endif - } - expand_regions(); - std::queue> workqueue; -#if 0 - std::vector> orig; - if (ctx->debug) - for (auto c : p->solve_cells) - orig.emplace_back(p->cell_locs[c->name].rawx, p->cell_locs[c->name].rawy); -#endif - for (auto &r : regions) { - if (merged_regions.count(r.id)) - continue; -#if 0 - for (auto t : sorted(beltype)) { - log_info("%s (%d, %d) |_> (%d, %d) %d/%d\n", t.c_str(ctx), r.x0, r.y0, r.x1, r.y1, - r.cells.at(type_index.at(t)), r.bels.at(type_index.at(t))); - } - -#endif - workqueue.emplace(r.id, false); - } - while (!workqueue.empty()) { - auto front = workqueue.front(); - workqueue.pop(); - auto &r = regions.at(front.first); - if (std::all_of(r.cells.begin(), r.cells.end(), [](int x) { return x == 0; })) - continue; - auto res = cut_region(r, front.second); - if (res) { - workqueue.emplace(res->first, !front.second); - workqueue.emplace(res->second, !front.second); - } else { - // Try the other dir, in case stuck in one direction only - auto res2 = cut_region(r, !front.second); - if (res2) { - workqueue.emplace(res2->first, front.second); - workqueue.emplace(res2->second, front.second); - } - } - } -#if 0 - if (ctx->debug) { - std::ofstream sp("spread" + std::to_string(seq) + ".csv"); - for (size_t i = 0; i < p->solve_cells.size(); i++) { - auto &c = p->solve_cells.at(i); - if (c->type != beltype) - continue; - sp << orig.at(i).first << "," << orig.at(i).second << "," << p->cell_locs[c->name].rawx << "," << p->cell_locs[c->name].rawy << std::endl; - } - std::ofstream oc("cells" + std::to_string(seq) + ".csv"); - for (size_t y = 0; y <= p->max_y; y++) { - for (size_t x = 0; x <= p->max_x; x++) { - oc << cells_at_location.at(x).at(y).size() << ", "; - } - oc << std::endl; - } - ++seq; - } -#endif - auto endt = std::chrono::high_resolution_clock::now(); - p->cl_time += std::chrono::duration(endt - startt).count(); - } - - private: - HeAPPlacer *p; - Context *ctx; - pool buckets; - dict type_index; - std::vector>> occupancy; - std::vector> groups; - std::vector> chaines; - std::map cell_extents; - - std::vector>> *> fb; - - std::vector regions; - pool merged_regions; - // Cells at a location, sorted by real (not integer) x and y - std::vector>> cells_at_location; - - int occ_at(int x, int y, int type) { return occupancy.at(x).at(y).at(type); } - - int bels_at(int x, int y, int type) - { - if (x >= int(fb.at(type)->size()) || y >= int(fb.at(type)->at(x).size())) - return 0; - return int(fb.at(type)->at(x).at(y).size()); - } - - bool is_cell_fixed(const CellInfo &cell) const - { - return buckets.count(ctx->getBelBucketForCellType(cell.type)) == 0; - } - - size_t cell_index(const CellInfo &cell) const { return type_index.at(ctx->getBelBucketForCellType(cell.type)); } - - void init() - { - occupancy.resize(p->max_x + 1, - std::vector>(p->max_y + 1, std::vector(buckets.size(), 0))); - groups.resize(p->max_x + 1, std::vector(p->max_y + 1, -1)); - chaines.resize(p->max_x + 1, std::vector(p->max_y + 1)); - cells_at_location.resize(p->max_x + 1, std::vector>(p->max_y + 1)); - for (int x = 0; x <= p->max_x; x++) - for (int y = 0; y <= p->max_y; y++) { - for (int t = 0; t < int(buckets.size()); t++) { - occupancy.at(x).at(y).at(t) = 0; - } - groups.at(x).at(y) = -1; - chaines.at(x).at(y) = {x, y, x, y}; - } - - auto set_chain_ext = [&](IdString cell, int x, int y) { - if (!cell_extents.count(cell)) - cell_extents[cell] = {x, y, x, y}; - else { - cell_extents[cell].x0 = std::min(cell_extents[cell].x0, x); - cell_extents[cell].y0 = std::min(cell_extents[cell].y0, y); - cell_extents[cell].x1 = std::max(cell_extents[cell].x1, x); - cell_extents[cell].y1 = std::max(cell_extents[cell].y1, y); - } - }; - - for (auto &cell_loc : p->cell_locs) { - IdString cell_name = cell_loc.first; - const CellInfo &cell = *ctx->cells.at(cell_name); - const CellLocation &loc = cell_loc.second; - if (is_cell_fixed(cell)) { - continue; - } - - if (cell.belStrength > STRENGTH_STRONG) { - continue; - } - - occupancy.at(cell_loc.second.x).at(cell_loc.second.y).at(cell_index(cell))++; - - // Compute ultimate extent of each chain root - if (cell.cluster != ClusterId()) { - set_chain_ext(ctx->getClusterRootCell(cell.cluster)->name, loc.x, loc.y); - } - } - - for (auto &cell_loc : p->cell_locs) { - IdString cell_name = cell_loc.first; - const CellInfo &cell = *ctx->cells.at(cell_name); - const CellLocation &loc = cell_loc.second; - if (is_cell_fixed(cell)) { - continue; - } - - if (cell.belStrength > STRENGTH_STRONG) { - continue; - } - - // Transfer chain extents to the actual chains structure - ChainExtent *ce = nullptr; - if (cell.cluster != ClusterId()) { - ce = &(cell_extents.at(ctx->getClusterRootCell(cell.cluster)->name)); - } - - if (ce) { - auto &lce = chaines.at(loc.x).at(loc.y); - lce.x0 = std::min(lce.x0, ce->x0); - lce.y0 = std::min(lce.y0, ce->y0); - lce.x1 = std::max(lce.x1, ce->x1); - lce.y1 = std::max(lce.y1, ce->y1); - } - } - - for (auto cell : p->solve_cells) { - if (is_cell_fixed(*cell)) { - continue; - } - - cells_at_location.at(p->cell_locs.at(cell->name).x).at(p->cell_locs.at(cell->name).y).push_back(cell); - } - } - - void merge_regions(SpreaderRegion &merged, SpreaderRegion &mergee) - { - // Prevent grow_region from recursing while doing this - for (int x = mergee.x0; x <= mergee.x1; x++) { - for (int y = mergee.y0; y <= mergee.y1; y++) { - // log_info("%d %d\n", groups.at(x).at(y), mergee.id); - NPNR_ASSERT(groups.at(x).at(y) == mergee.id); - groups.at(x).at(y) = merged.id; - for (size_t t = 0; t < buckets.size(); t++) { - merged.cells.at(t) += occ_at(x, y, t); - merged.bels.at(t) += bels_at(x, y, t); - } - } - } - - merged_regions.insert(mergee.id); - grow_region(merged, mergee.x0, mergee.y0, mergee.x1, mergee.y1); - } - - void grow_region(SpreaderRegion &r, int x0, int y0, int x1, int y1, bool init = false) - { - // log_info("growing to (%d, %d) |_> (%d, %d)\n", x0, y0, x1, y1); - if ((x0 >= r.x0 && y0 >= r.y0 && x1 <= r.x1 && y1 <= r.y1) || init) - return; - int old_x0 = r.x0 + (init ? 1 : 0), old_y0 = r.y0, old_x1 = r.x1, old_y1 = r.y1; - r.x0 = std::min(r.x0, x0); - r.y0 = std::min(r.y0, y0); - r.x1 = std::max(r.x1, x1); - r.y1 = std::max(r.y1, y1); - - auto process_location = [&](int x, int y) { - // Merge with any overlapping regions - if (groups.at(x).at(y) == -1) { - for (size_t t = 0; t < buckets.size(); t++) { - r.bels.at(t) += bels_at(x, y, t); - r.cells.at(t) += occ_at(x, y, t); - } - } - - if (groups.at(x).at(y) != -1 && groups.at(x).at(y) != r.id) - merge_regions(r, regions.at(groups.at(x).at(y))); - groups.at(x).at(y) = r.id; - // Grow to cover any chains - auto &chaine = chaines.at(x).at(y); - grow_region(r, chaine.x0, chaine.y0, chaine.x1, chaine.y1); - }; - for (int x = r.x0; x < old_x0; x++) - for (int y = r.y0; y <= r.y1; y++) - process_location(x, y); - for (int x = old_x1 + 1; x <= x1; x++) - for (int y = r.y0; y <= r.y1; y++) - process_location(x, y); - for (int y = r.y0; y < old_y0; y++) - for (int x = r.x0; x <= r.x1; x++) - process_location(x, y); - for (int y = old_y1 + 1; y <= r.y1; y++) - for (int x = r.x0; x <= r.x1; x++) - process_location(x, y); - } - - void find_overused_regions() - { - for (int x = 0; x <= p->max_x; x++) - for (int y = 0; y <= p->max_y; y++) { - // Either already in a group, or not overutilised. Ignore - if (groups.at(x).at(y) != -1) - continue; - bool overutilised = false; - for (size_t t = 0; t < buckets.size(); t++) { - if (occ_at(x, y, t) > bels_at(x, y, t)) { - overutilised = true; - break; - } - } - - if (!overutilised) - continue; - // log_info("%d %d %d\n", x, y, occ_at(x, y)); - int id = int(regions.size()); - groups.at(x).at(y) = id; - SpreaderRegion reg; - reg.id = id; - reg.x0 = reg.x1 = x; - reg.y0 = reg.y1 = y; - for (size_t t = 0; t < buckets.size(); t++) { - reg.bels.push_back(bels_at(x, y, t)); - reg.cells.push_back(occ_at(x, y, t)); - } - // Make sure we cover carries, etc - grow_region(reg, reg.x0, reg.y0, reg.x1, reg.y1, true); - - bool expanded = true; - while (expanded) { - expanded = false; - // Keep trying expansion in x and y, until we find no over-occupancy cells - // or hit grouped cells - - // First try expanding in x - if (reg.x1 < p->max_x) { - bool over_occ_x = false; - for (int y1 = reg.y0; y1 <= reg.y1; y1++) { - for (size_t t = 0; t < buckets.size(); t++) { - if (occ_at(reg.x1 + 1, y1, t) > bels_at(reg.x1 + 1, y1, t)) { - over_occ_x = true; - break; - } - } - } - if (over_occ_x) { - expanded = true; - grow_region(reg, reg.x0, reg.y0, reg.x1 + 1, reg.y1); - } - } - - if (reg.y1 < p->max_y) { - bool over_occ_y = false; - for (int x1 = reg.x0; x1 <= reg.x1; x1++) { - for (size_t t = 0; t < buckets.size(); t++) { - if (occ_at(x1, reg.y1 + 1, t) > bels_at(x1, reg.y1 + 1, t)) { - over_occ_y = true; - break; - } - } - } - if (over_occ_y) { - expanded = true; - grow_region(reg, reg.x0, reg.y0, reg.x1, reg.y1 + 1); - } - } - } - regions.push_back(reg); - } - } - - void expand_regions() - { - std::queue overu_regions; - float beta = p->cfg.beta; - for (auto &r : regions) { - if (!merged_regions.count(r.id) && r.overused(beta)) - overu_regions.push(r.id); - } - while (!overu_regions.empty()) { - int rid = overu_regions.front(); - overu_regions.pop(); - if (merged_regions.count(rid)) - continue; - auto ® = regions.at(rid); - while (reg.overused(beta)) { - bool changed = false; - for (int j = 0; j < p->cfg.spread_scale_x; j++) { - if (reg.x0 > 0) { - grow_region(reg, reg.x0 - 1, reg.y0, reg.x1, reg.y1); - changed = true; - if (!reg.overused(beta)) - break; - } - if (reg.x1 < p->max_x) { - grow_region(reg, reg.x0, reg.y0, reg.x1 + 1, reg.y1); - changed = true; - if (!reg.overused(beta)) - break; - } - } - for (int j = 0; j < p->cfg.spread_scale_y; j++) { - if (reg.y0 > 0) { - grow_region(reg, reg.x0, reg.y0 - 1, reg.x1, reg.y1); - changed = true; - if (!reg.overused(beta)) - break; - } - if (reg.y1 < p->max_y) { - grow_region(reg, reg.x0, reg.y0, reg.x1, reg.y1 + 1); - changed = true; - if (!reg.overused(beta)) - break; - } - } - if (!changed) { - for (auto bucket : buckets) { - if (reg.cells > reg.bels) { - IdString bucket_name = ctx->getBelBucketName(bucket); - log_error("Failed to expand region (%d, %d) |_> (%d, %d) of %d %ss\n", reg.x0, reg.y0, - reg.x1, reg.y1, reg.cells.at(type_index.at(bucket)), bucket_name.c_str(ctx)); - } - } - break; - } - } - } - } - - // Implementation of the recursive cut-based spreading as described in the HeAP paper - // Note we use "left" to mean "-x/-y" depending on dir and "right" to mean "+x/+y" depending on dir - - std::vector cut_cells; - - boost::optional> cut_region(SpreaderRegion &r, bool dir) - { - cut_cells.clear(); - auto &cal = cells_at_location; - int total_cells = 0, total_bels = 0; - for (int x = r.x0; x <= r.x1; x++) { - for (int y = r.y0; y <= r.y1; y++) { - std::copy(cal.at(x).at(y).begin(), cal.at(x).at(y).end(), std::back_inserter(cut_cells)); - for (size_t t = 0; t < buckets.size(); t++) - total_bels += bels_at(x, y, t); - } - } - for (auto &cell : cut_cells) { - total_cells += p->chain_size.count(cell->name) ? p->chain_size.at(cell->name) : 1; - } - std::sort(cut_cells.begin(), cut_cells.end(), [&](const CellInfo *a, const CellInfo *b) { - return dir ? (p->cell_locs.at(a->name).rawy < p->cell_locs.at(b->name).rawy) - : (p->cell_locs.at(a->name).rawx < p->cell_locs.at(b->name).rawx); - }); - - if (cut_cells.size() < 2) - return {}; - // Find the cells midpoint, counting chains in terms of their total size - making the initial source cut - int pivot_cells = 0; - int pivot = 0; - for (auto &cell : cut_cells) { - pivot_cells += p->chain_size.count(cell->name) ? p->chain_size.at(cell->name) : 1; - if (pivot_cells >= total_cells / 2) - break; - pivot++; - } - if (pivot >= int(cut_cells.size())) { - pivot = int(cut_cells.size()) - 1; - } - // log_info("orig pivot %d/%d lc %d rc %d\n", pivot, int(cut_cells.size()), pivot_cells, total_cells - - // pivot_cells); - - // Find the clearance required either side of the pivot - int clearance_l = 0, clearance_r = 0; - for (size_t i = 0; i < cut_cells.size(); i++) { - int size; - if (cell_extents.count(cut_cells.at(i)->name)) { - auto &ce = cell_extents.at(cut_cells.at(i)->name); - size = dir ? (ce.y1 - ce.y0 + 1) : (ce.x1 - ce.x0 + 1); - } else { - size = 1; - } - if (int(i) < pivot) - clearance_l = std::max(clearance_l, size); - else - clearance_r = std::max(clearance_r, size); - } - // Find the target cut that minimises difference in utilisation, whilst trying to ensure that all chains - // still fit - - // First trim the boundaries of the region in the axis-of-interest, skipping any rows/cols without any - // bels of the appropriate type - int trimmed_l = dir ? r.y0 : r.x0, trimmed_r = dir ? r.y1 : r.x1; - while (trimmed_l < (dir ? r.y1 : r.x1)) { - bool have_bels = false; - for (int i = dir ? r.x0 : r.y0; i <= (dir ? r.x1 : r.y1); i++) { - for (size_t t = 0; t < buckets.size(); t++) { - if (bels_at(dir ? i : trimmed_l, dir ? trimmed_l : i, t) > 0) { - have_bels = true; - break; - } - } - } - - if (have_bels) - break; - - trimmed_l++; - } - while (trimmed_r > (dir ? r.y0 : r.x0)) { - bool have_bels = false; - for (int i = dir ? r.x0 : r.y0; i <= (dir ? r.x1 : r.y1); i++) { - for (size_t t = 0; t < buckets.size(); t++) { - if (bels_at(dir ? i : trimmed_r, dir ? trimmed_r : i, t) > 0) { - have_bels = true; - break; - } - } - } - - if (have_bels) - break; - - trimmed_r--; - } - // log_info("tl %d tr %d cl %d cr %d\n", trimmed_l, trimmed_r, clearance_l, clearance_r); - if ((trimmed_r - trimmed_l + 1) <= std::max(clearance_l, clearance_r)) - return {}; - // Now find the initial target cut that minimises utilisation imbalance, whilst - // meeting the clearance requirements for any large macros - std::vector left_cells_v(buckets.size(), 0), right_cells_v(buckets.size(), 0); - std::vector left_bels_v(buckets.size(), 0), right_bels_v(r.bels); - for (int i = 0; i <= pivot; i++) - left_cells_v.at(cell_index(*cut_cells.at(i))) += - p->chain_size.count(cut_cells.at(i)->name) ? p->chain_size.at(cut_cells.at(i)->name) : 1; - for (int i = pivot + 1; i < int(cut_cells.size()); i++) - right_cells_v.at(cell_index(*cut_cells.at(i))) += - p->chain_size.count(cut_cells.at(i)->name) ? p->chain_size.at(cut_cells.at(i)->name) : 1; - - int best_tgt_cut = -1; - double best_deltaU = std::numeric_limits::max(); - // std::pair target_cut_bels; - std::vector slither_bels(buckets.size(), 0); - for (int i = trimmed_l; i <= trimmed_r; i++) { - for (size_t t = 0; t < buckets.size(); t++) - slither_bels.at(t) = 0; - for (int j = dir ? r.x0 : r.y0; j <= (dir ? r.x1 : r.y1); j++) { - for (size_t t = 0; t < buckets.size(); t++) - slither_bels.at(t) += dir ? bels_at(j, i, t) : bels_at(i, j, t); - } - for (size_t t = 0; t < buckets.size(); t++) { - left_bels_v.at(t) += slither_bels.at(t); - right_bels_v.at(t) -= slither_bels.at(t); - } - - if (((i - trimmed_l) + 1) >= clearance_l && ((trimmed_r - i) + 1) >= clearance_r) { - // Solution is potentially valid - double aU = 0; - for (size_t t = 0; t < buckets.size(); t++) - aU += (left_cells_v.at(t) + right_cells_v.at(t)) * - std::abs(double(left_cells_v.at(t)) / double(std::max(left_bels_v.at(t), 1)) - - double(right_cells_v.at(t)) / double(std::max(right_bels_v.at(t), 1))); - if (aU < best_deltaU) { - best_deltaU = aU; - best_tgt_cut = i; - } - } - } - if (best_tgt_cut == -1) - return {}; - // left_bels = target_cut_bels.first; - // right_bels = target_cut_bels.second; - for (size_t t = 0; t < buckets.size(); t++) { - left_bels_v.at(t) = 0; - right_bels_v.at(t) = 0; - } - for (int x = r.x0; x <= (dir ? r.x1 : best_tgt_cut); x++) - for (int y = r.y0; y <= (dir ? best_tgt_cut : r.y1); y++) { - for (size_t t = 0; t < buckets.size(); t++) { - left_bels_v.at(t) += bels_at(x, y, t); - } - } - for (int x = dir ? r.x0 : (best_tgt_cut + 1); x <= r.x1; x++) - for (int y = dir ? (best_tgt_cut + 1) : r.y0; y <= r.y1; y++) { - for (size_t t = 0; t < buckets.size(); t++) { - right_bels_v.at(t) += bels_at(x, y, t); - } - } - if (std::accumulate(left_bels_v.begin(), left_bels_v.end(), 0) == 0 || - std::accumulate(right_bels_v.begin(), right_bels_v.end(), 0) == 0) - return {}; - - // Perturb the source cut to eliminate overutilisation - auto is_part_overutil = [&](bool r) { - double delta = 0; - for (size_t t = 0; t < left_cells_v.size(); t++) { - delta += double(left_cells_v.at(t)) / double(std::max(left_bels_v.at(t), 1)) - - double(right_cells_v.at(t)) / double(std::max(right_bels_v.at(t), 1)); - } - return r ? delta < 0 : delta > 0; - }; - while (pivot > 0 && is_part_overutil(false)) { - auto &move_cell = cut_cells.at(pivot); - int size = p->chain_size.count(move_cell->name) ? p->chain_size.at(move_cell->name) : 1; - left_cells_v.at(cell_index(*cut_cells.at(pivot))) -= size; - right_cells_v.at(cell_index(*cut_cells.at(pivot))) += size; - pivot--; - } - while (pivot < int(cut_cells.size()) - 1 && is_part_overutil(true)) { - auto &move_cell = cut_cells.at(pivot + 1); - int size = p->chain_size.count(move_cell->name) ? p->chain_size.at(move_cell->name) : 1; - left_cells_v.at(cell_index(*cut_cells.at(pivot))) += size; - right_cells_v.at(cell_index(*cut_cells.at(pivot))) -= size; - pivot++; - } - - // Split regions into bins, and then spread cells by linear interpolation within those bins - auto spread_binlerp = [&](int cells_start, int cells_end, double area_l, double area_r) { - int N = cells_end - cells_start; - if (N <= 2) { - for (int i = cells_start; i < cells_end; i++) { - auto &pos = dir ? p->cell_locs.at(cut_cells.at(i)->name).rawy - : p->cell_locs.at(cut_cells.at(i)->name).rawx; - pos = area_l + i * ((area_r - area_l) / N); - } - return; - } - // Split region into up to 10 (K) bins - int K = std::min(N, 10); - std::vector> bin_bounds; // [(cell start, area start)] - bin_bounds.emplace_back(cells_start, area_l); - for (int i = 1; i < K; i++) - bin_bounds.emplace_back(cells_start + (N * i) / K, area_l + ((area_r - area_l + 0.99) * i) / K); - bin_bounds.emplace_back(cells_end, area_r + 0.99); - for (int i = 0; i < K; i++) { - auto &bl = bin_bounds.at(i), br = bin_bounds.at(i + 1); - double orig_left = dir ? p->cell_locs.at(cut_cells.at(bl.first)->name).rawy - : p->cell_locs.at(cut_cells.at(bl.first)->name).rawx; - double orig_right = dir ? p->cell_locs.at(cut_cells.at(br.first - 1)->name).rawy - : p->cell_locs.at(cut_cells.at(br.first - 1)->name).rawx; - double m = (br.second - bl.second) / std::max(0.00001, orig_right - orig_left); - for (int j = bl.first; j < br.first; j++) { - Region *cr = cut_cells.at(j)->region; - if (cr != nullptr) { - // Limit spreading bounds to constraint region; if applicable - double brsc = p->limit_to_reg(cr, br.second, dir); - double blsc = p->limit_to_reg(cr, bl.second, dir); - double mr = (brsc - blsc) / std::max(0.00001, orig_right - orig_left); - auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy - : p->cell_locs.at(cut_cells.at(j)->name).rawx; - NPNR_ASSERT(pos >= orig_left && pos <= orig_right); - pos = blsc + mr * (pos - orig_left); - } else { - auto &pos = dir ? p->cell_locs.at(cut_cells.at(j)->name).rawy - : p->cell_locs.at(cut_cells.at(j)->name).rawx; - NPNR_ASSERT(pos >= orig_left && pos <= orig_right); - pos = bl.second + m * (pos - orig_left); - } - } - } - }; - spread_binlerp(0, pivot + 1, trimmed_l, best_tgt_cut); - spread_binlerp(pivot + 1, int(cut_cells.size()), best_tgt_cut + 1, trimmed_r); - // Update various data structures - for (int x = r.x0; x <= r.x1; x++) - for (int y = r.y0; y <= r.y1; y++) { - cells_at_location.at(x).at(y).clear(); - } - for (auto cell : cut_cells) { - auto &cl = p->cell_locs.at(cell->name); - cl.x = std::min(r.x1, std::max(r.x0, int(cl.rawx))); - cl.y = std::min(r.y1, std::max(r.y0, int(cl.rawy))); - cells_at_location.at(cl.x).at(cl.y).push_back(cell); - } - SpreaderRegion rl, rr; - rl.id = int(regions.size()); - rl.x0 = r.x0; - rl.y0 = r.y0; - rl.x1 = dir ? r.x1 : best_tgt_cut; - rl.y1 = dir ? best_tgt_cut : r.y1; - rl.cells = left_cells_v; - rl.bels = left_bels_v; - rr.id = int(regions.size()) + 1; - rr.x0 = dir ? r.x0 : (best_tgt_cut + 1); - rr.y0 = dir ? (best_tgt_cut + 1) : r.y0; - rr.x1 = r.x1; - rr.y1 = r.y1; - rr.cells = right_cells_v; - rr.bels = right_bels_v; - regions.push_back(rl); - regions.push_back(rr); - for (int x = rl.x0; x <= rl.x1; x++) - for (int y = rl.y0; y <= rl.y1; y++) - groups.at(x).at(y) = rl.id; - for (int x = rr.x0; x <= rr.x1; x++) - for (int y = rr.y0; y <= rr.y1; y++) - groups.at(x).at(y) = rr.id; - return std::make_pair(rl.id, rr.id); - }; - }; - typedef decltype(CellInfo::udata) cell_udata_t; - cell_udata_t dont_solve = std::numeric_limits::max(); -}; -int HeAPPlacer::CutSpreader::seq = 0; - -bool placer_heap(Context *ctx, PlacerHeapCfg cfg) { return HeAPPlacer(ctx, cfg).place(); } - -PlacerHeapCfg::PlacerHeapCfg(Context *ctx) -{ - alpha = ctx->setting("placerHeap/alpha"); - beta = ctx->setting("placerHeap/beta"); - criticalityExponent = ctx->setting("placerHeap/criticalityExponent"); - timingWeight = ctx->setting("placerHeap/timingWeight"); - parallelRefine = ctx->setting("placerHeap/parallelRefine", false); - - timing_driven = ctx->setting("timing_driven"); - solverTolerance = 1e-5; - placeAllAtOnce = false; - - hpwl_scale_x = 1; - hpwl_scale_y = 1; - spread_scale_x = 1; - spread_scale_y = 1; -} - -NEXTPNR_NAMESPACE_END - -#else - -#include "log.h" -#include "nextpnr.h" -#include "placer_heap.h" - -NEXTPNR_NAMESPACE_BEGIN -bool placer_heap(Context *ctx, PlacerHeapCfg cfg) -{ - log_error("nextpnr was built without the HeAP placer\n"); - return false; -} - -PlacerHeapCfg::PlacerHeapCfg(Context *ctx) {} - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/placer_heap.h b/common/placer_heap.h deleted file mode 100644 index 9c62869e..00000000 --- a/common/placer_heap.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2019 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. - * - * [[cite]] HeAP - * Analytical Placement for Heterogeneous FPGAs, Marcel Gort and Jason H. Anderson - * https://janders.eecg.utoronto.ca/pdfs/marcelfpl12.pdf - * - * [[cite]] SimPL - * SimPL: An Effective Placement Algorithm, Myung-Chul Kim, Dong-Jin Lee and Igor L. Markov - * http://www.ece.umich.edu/cse/awards/pdfs/iccad10-simpl.pdf - */ - -#ifndef PLACER_HEAP_H -#define PLACER_HEAP_H -#include "log.h" -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct PlacerHeapCfg -{ - PlacerHeapCfg(Context *ctx); - - float alpha, beta; - float criticalityExponent; - float timingWeight; - bool timing_driven; - float solverTolerance; - bool placeAllAtOnce; - bool parallelRefine; - - int hpwl_scale_x, hpwl_scale_y; - int spread_scale_x, spread_scale_y; - - // These cell types will be randomly locked to prevent singular matrices - pool ioBufTypes; - // These cell types are part of the same unit (e.g. slices split into - // components) so will always be spread together - std::vector> cellGroups; -}; - -extern bool placer_heap(Context *ctx, PlacerHeapCfg cfg); -NEXTPNR_NAMESPACE_END -#endif diff --git a/common/property.cc b/common/property.cc deleted file mode 100644 index 6c30436d..00000000 --- a/common/property.cc +++ /dev/null @@ -1,80 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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 "property.h" - -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -Property::Property() : is_string(false), str(""), intval(0) {} - -Property::Property(int64_t intval, int width) : is_string(false), intval(intval) -{ - str.reserve(width); - for (int i = 0; i < width; i++) - str.push_back((intval & (1ULL << i)) ? S1 : S0); -} - -Property::Property(const std::string &strval) : is_string(true), str(strval), intval(0xDEADBEEF) {} - -Property::Property(State bit) : is_string(false), str(std::string("") + char(bit)), intval(bit == S1) {} - -std::string Property::to_string() const -{ - if (is_string) { - std::string result = str; - int state = 0; - for (char c : str) { - if (state == 0) { - if (c == '0' || c == '1' || c == 'x' || c == 'z') - state = 0; - else if (c == ' ') - state = 1; - else - state = 2; - } else if (state == 1 && c != ' ') - state = 2; - } - if (state < 2) - result += " "; - return result; - } else { - return std::string(str.rbegin(), str.rend()); - } -} - -Property Property::from_string(const std::string &s) -{ - Property p; - - size_t cursor = s.find_first_not_of("01xz"); - if (cursor == std::string::npos) { - p.str = std::string(s.rbegin(), s.rend()); - p.is_string = false; - p.update_intval(); - } else if (s.find_first_not_of(' ', cursor) == std::string::npos) { - p = Property(s.substr(0, s.size() - 1)); - } else { - p = Property(s); - } - return p; -} - -NEXTPNR_NAMESPACE_END diff --git a/common/property.h b/common/property.h deleted file mode 100644 index 814b2cac..00000000 --- a/common/property.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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 PROPERTY_H -#define PROPERTY_H - -#include -#include -#include -#include - -#include "nextpnr_assertions.h" -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct Property -{ - enum State : char - { - S0 = '0', - S1 = '1', - Sx = 'x', - Sz = 'z' - }; - - Property(); - Property(int64_t intval, int width = 32); - Property(const std::string &strval); - Property(State bit); - Property &operator=(const Property &other) = default; - - bool is_string; - - // The string literal (for string values), or a string of [01xz] (for numeric values) - std::string str; - // The lower 64 bits (for numeric values), unused for string values - int64_t intval; - - void update_intval() - { - intval = 0; - for (int i = 0; i < int(str.size()); i++) { - NPNR_ASSERT(str[i] == S0 || str[i] == S1 || str[i] == Sx || str[i] == Sz); - if ((str[i] == S1) && i < 64) - intval |= (1ULL << i); - } - } - - int64_t as_int64() const - { - NPNR_ASSERT(!is_string); - return intval; - } - std::vector as_bits() const - { - std::vector result; - result.reserve(str.size()); - NPNR_ASSERT(!is_string); - for (auto c : str) - result.push_back(c == S1); - return result; - } - const std::string &as_string() const - { - NPNR_ASSERT(is_string); - return str; - } - const char *c_str() const - { - NPNR_ASSERT(is_string); - return str.c_str(); - } - size_t size() const { return is_string ? 8 * str.size() : str.size(); } - double as_double() const - { - NPNR_ASSERT(is_string); - return std::stod(str); - } - bool as_bool() const - { - if (int(str.size()) <= 64) - return intval != 0; - else - return std::any_of(str.begin(), str.end(), [](char c) { return c == S1; }); - } - bool is_fully_def() const - { - return !is_string && std::all_of(str.begin(), str.end(), [](char c) { return c == S0 || c == S1; }); - } - Property extract(int offset, int len, State padding = State::S0) const - { - Property ret; - ret.is_string = false; - ret.str.reserve(len); - for (int i = offset; i < offset + len; i++) - ret.str.push_back(i < int(str.size()) ? str[i] : char(padding)); - ret.update_intval(); - return ret; - } - // Convert to a string representation, escaping literal strings matching /^[01xz]* *$/ by adding a space at the end, - // to disambiguate from binary strings - std::string to_string() const; - // Convert a string of four-value binary [01xz], or a literal string escaped according to the above rule - // to a Property - static Property from_string(const std::string &s); -}; - -inline bool operator==(const Property &a, const Property &b) { return a.is_string == b.is_string && a.str == b.str; } -inline bool operator!=(const Property &a, const Property &b) { return a.is_string != b.is_string || a.str != b.str; } - -NEXTPNR_NAMESPACE_END - -#endif /* PROPERTY_H */ diff --git a/common/pybindings.cc b/common/pybindings.cc deleted file mode 100644 index 9a783eb4..00000000 --- a/common/pybindings.cc +++ /dev/null @@ -1,362 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * 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 "pybindings.h" -#include "arch_pybindings.h" -#include "json_frontend.h" -#include "log.h" -#include "nextpnr.h" - -#include -#include -#include -NEXTPNR_NAMESPACE_BEGIN - -// Required to determine concatenated module name (which differs for different -// archs) -#define PASTER(x, y) x##_##y -#define EVALUATOR(x, y) PASTER(x, y) -#define MODULE_NAME EVALUATOR(nextpnrpy, ARCHNAME) -#define PYINIT_MODULE_NAME EVALUATOR(&PyInit_nextpnrpy, ARCHNAME) -#define STRINGIFY(x) #x -#define TOSTRING(x) STRINGIFY(x) - -// Architecture-specific bindings should be created in the below function, which -// must be implemented in all architectures -void arch_wrap_python(py::module &m); - -bool operator==(const PortRef &a, const PortRef &b) { return (a.cell == b.cell) && (a.port == b.port); } - -// Load a JSON file into a design -void parse_json_shim(std::string filename, Context &d) -{ - std::ifstream inf(filename); - if (!inf) - throw std::runtime_error("failed to open file " + filename); - parse_json(inf, filename, &d); -} - -// Create a new Chip and load design from json file -Context *load_design_shim(std::string filename, ArchArgs args) -{ - Context *d = new Context(args); - parse_json_shim(filename, *d); - return d; -} - -namespace PythonConversion { -template <> struct string_converter -{ - inline PortRef from_str(Context *ctx, std::string name) { NPNR_ASSERT_FALSE("PortRef from_str not implemented"); } - - inline std::string to_str(Context *ctx, const PortRef &pr) - { - return pr.cell->name.str(ctx) + "." + pr.port.str(ctx); - } -}; - -template <> struct string_converter -{ - inline Property from_str(Context *ctx, std::string s) { return Property::from_string(s); } - - inline std::string to_str(Context *ctx, Property p) { return p.to_string(); } -}; - -} // namespace PythonConversion - -std::string loc_repr_py(Loc loc) { return stringf("Loc(%d, %d, %d)", loc.x, loc.y, loc.z); } - -PYBIND11_EMBEDDED_MODULE(MODULE_NAME, m) -{ - py::register_exception_translator([](std::exception_ptr p) { - try { - if (p) - std::rethrow_exception(p); - } catch (const assertion_failure &e) { - PyErr_SetString(PyExc_AssertionError, e.what()); - } - }); - - using namespace PythonConversion; - - py::enum_(m, "GraphicElementType") - .value("TYPE_NONE", GraphicElement::TYPE_NONE) - .value("TYPE_LINE", GraphicElement::TYPE_LINE) - .value("TYPE_ARROW", GraphicElement::TYPE_ARROW) - .value("TYPE_BOX", GraphicElement::TYPE_BOX) - .value("TYPE_CIRCLE", GraphicElement::TYPE_CIRCLE) - .value("TYPE_LABEL", GraphicElement::TYPE_LABEL) - .export_values(); - - py::enum_(m, "GraphicElementStyle") - .value("STYLE_GRID", GraphicElement::STYLE_GRID) - .value("STYLE_FRAME", GraphicElement::STYLE_FRAME) - .value("STYLE_HIDDEN", GraphicElement::STYLE_HIDDEN) - .value("STYLE_INACTIVE", GraphicElement::STYLE_INACTIVE) - .value("STYLE_ACTIVE", GraphicElement::STYLE_ACTIVE) - .export_values(); - - py::class_(m, "GraphicElement") - .def(py::init(), - py::arg("type"), py::arg("style"), py::arg("x1"), py::arg("y1"), py::arg("x2"), py::arg("y2"), - py::arg("z")) - .def_readwrite("type", &GraphicElement::type) - .def_readwrite("x1", &GraphicElement::x1) - .def_readwrite("y1", &GraphicElement::y1) - .def_readwrite("x2", &GraphicElement::x2) - .def_readwrite("y2", &GraphicElement::y2) - .def_readwrite("text", &GraphicElement::text); - - py::enum_(m, "PortType") - .value("PORT_IN", PORT_IN) - .value("PORT_OUT", PORT_OUT) - .value("PORT_INOUT", PORT_INOUT) - .export_values(); - - py::enum_(m, "PlaceStrength") - .value("STRENGTH_NONE", STRENGTH_NONE) - .value("STRENGTH_WEAK", STRENGTH_WEAK) - .value("STRENGTH_STRONG", STRENGTH_STRONG) - .value("STRENGTH_FIXED", STRENGTH_FIXED) - .value("STRENGTH_LOCKED", STRENGTH_LOCKED) - .value("STRENGTH_USER", STRENGTH_USER) - .export_values(); - - py::class_(m, "DelayPair") - .def(py::init<>()) - .def(py::init()) - .def(py::init()) - .def_readwrite("min_delay", &DelayPair::min_delay) - .def_readwrite("max_delay", &DelayPair::max_delay) - .def("minDelay", &DelayPair::minDelay) - .def("maxDelay", &DelayPair::maxDelay); - - py::class_(m, "DelayQuad") - .def(py::init<>()) - .def(py::init()) - .def(py::init()) - .def(py::init()) - .def(py::init()) - .def_readwrite("rise", &DelayQuad::rise) - .def_readwrite("fall", &DelayQuad::fall) - .def("minDelay", &DelayQuad::minDelay) - .def("minRiseDelay", &DelayQuad::minRiseDelay) - .def("minFallDelay", &DelayQuad::minFallDelay) - .def("maxDelay", &DelayQuad::maxDelay) - .def("maxRiseDelay", &DelayQuad::maxRiseDelay) - .def("maxFallDelay", &DelayQuad::maxFallDelay) - .def("delayPair", &DelayQuad::delayPair); - - typedef dict AttrMap; - typedef dict PortMap; - typedef dict IdIdMap; - typedef dict> RegionMap; - - py::class_(m, "BaseCtx"); - - auto loc_cls = py::class_(m, "Loc") - .def(py::init()) - .def_readwrite("x", &Loc::x) - .def_readwrite("y", &Loc::y) - .def_readwrite("z", &Loc::z) - .def("__repr__", loc_repr_py); - - auto ci_cls = py::class_>(m, "CellInfo"); - readwrite_wrapper, - conv_from_str>::def_wrap(ci_cls, "name"); - readwrite_wrapper, - conv_from_str>::def_wrap(ci_cls, "type"); - readonly_wrapper>::def_wrap( - ci_cls, "attrs"); - readonly_wrapper>::def_wrap( - ci_cls, "params"); - readonly_wrapper>::def_wrap( - ci_cls, "ports"); - readwrite_wrapper, - conv_from_str>::def_wrap(ci_cls, "bel"); - readwrite_wrapper, - pass_through>::def_wrap(ci_cls, "belStrength"); - - fn_wrapper_1a_v>::def_wrap( - ci_cls, "addInput"); - fn_wrapper_1a_v>::def_wrap(ci_cls, "addOutput"); - fn_wrapper_1a_v>::def_wrap( - ci_cls, "addInout"); - - fn_wrapper_2a_v, - conv_from_str>::def_wrap(ci_cls, "setParam"); - fn_wrapper_1a_v>::def_wrap(ci_cls, "unsetParam"); - fn_wrapper_2a_v, - conv_from_str>::def_wrap(ci_cls, "setAttr"); - fn_wrapper_1a_v>::def_wrap(ci_cls, "unsetAttr"); - - auto pi_cls = py::class_>(m, "PortInfo"); - readwrite_wrapper, - conv_from_str>::def_wrap(pi_cls, "name"); - readonly_wrapper>::def_wrap(pi_cls, - "net"); - readwrite_wrapper, - pass_through>::def_wrap(pi_cls, "type"); - - typedef indexed_store PortRefVector; - typedef dict WireMap; - typedef pool BelSet; - typedef pool WireSet; - - auto ni_cls = py::class_>(m, "NetInfo"); - readwrite_wrapper, - conv_from_str>::def_wrap(ni_cls, "name"); - readonly_wrapper>::def_wrap( - ni_cls, "driver"); - readonly_wrapper>::def_wrap( - ni_cls, "users"); - readonly_wrapper>::def_wrap(ni_cls, - "wires"); - - auto pr_cls = py::class_>(m, "PortRef"); - readonly_wrapper>::def_wrap(pr_cls, - "cell"); - readonly_wrapper>::def_wrap(pr_cls, - "port"); - readonly_wrapper>::def_wrap(pr_cls, - "budget"); - - auto pm_cls = py::class_>(m, "PipMap"); - readwrite_wrapper, - conv_from_str>::def_wrap(pm_cls, "pip"); - readwrite_wrapper, - pass_through>::def_wrap(pm_cls, "strength"); - - m.def("parse_json", parse_json_shim); - m.def("load_design", load_design_shim, py::return_value_policy::take_ownership); - - auto region_cls = py::class_>(m, "Region"); - readwrite_wrapper, - conv_from_str>::def_wrap(region_cls, "name"); - readwrite_wrapper, - pass_through>::def_wrap(region_cls, "constr_bels"); - readwrite_wrapper, - pass_through>::def_wrap(region_cls, "constr_bels"); - readwrite_wrapper, - pass_through>::def_wrap(region_cls, "constr_pips"); - readonly_wrapper>::def_wrap(region_cls, - "bels"); - readonly_wrapper>::def_wrap(region_cls, - "wires"); - - auto hierarchy_cls = py::class_>(m, "HierarchicalCell"); - readwrite_wrapper, conv_from_str>::def_wrap(hierarchy_cls, "name"); - readwrite_wrapper, conv_from_str>::def_wrap(hierarchy_cls, "type"); - readwrite_wrapper, conv_from_str>::def_wrap(hierarchy_cls, "parent"); - readwrite_wrapper, conv_from_str>::def_wrap(hierarchy_cls, "fullpath"); - - readonly_wrapper>::def_wrap(hierarchy_cls, "leaf_cells"); - readonly_wrapper>::def_wrap(hierarchy_cls, "nets"); - readonly_wrapper>::def_wrap(hierarchy_cls, "hier_cells"); - WRAP_MAP(m, AttrMap, conv_to_str, "AttrMap"); - WRAP_MAP(m, PortMap, wrap_context, "PortMap"); - WRAP_MAP(m, IdIdMap, conv_to_str, "IdIdMap"); - WRAP_MAP(m, WireMap, wrap_context, "WireMap"); - WRAP_MAP_UPTR(m, RegionMap, "RegionMap"); - - WRAP_INDEXSTORE(m, PortRefVector, wrap_context); - - typedef dict ClockFmaxMap; - WRAP_MAP(m, ClockFmaxMap, pass_through, "ClockFmaxMap"); - - auto clk_fmax_cls = py::class_(m, "ClockFmax") - .def_readonly("achieved", &ClockFmax::achieved) - .def_readonly("constraint", &ClockFmax::constraint); - - auto tmg_result_cls = py::class_>(m, "TimingResult"); - readonly_wrapper>::def_wrap(tmg_result_cls, "clock_fmax"); - arch_wrap_python(m); -} - -#ifdef MAIN_EXECUTABLE -static wchar_t *program; -#endif - -void (*python_sighandler)(int) = nullptr; - -void init_python(const char *executable) -{ -#ifdef MAIN_EXECUTABLE - program = Py_DecodeLocale(executable, NULL); - if (program == NULL) { - fprintf(stderr, "Fatal error: cannot decode executable filename\n"); - exit(1); - } - Py_SetProgramName(program); - py::initialize_interpreter(); - py::module::import(TOSTRING(MODULE_NAME)); - PyRun_SimpleString("from " TOSTRING(MODULE_NAME) " import *"); - python_sighandler = signal(SIGINT, SIG_DFL); -#endif -} - -void deinit_python() -{ -#ifdef MAIN_EXECUTABLE - py::finalize_interpreter(); - PyMem_RawFree(program); -#endif -} - -void execute_python_file(const char *python_file) -{ - try { - FILE *fp = fopen(python_file, "r"); - if (fp == NULL) { - fprintf(stderr, "Fatal error: file not found %s\n", python_file); - exit(1); - } - if (python_sighandler) - signal(SIGINT, python_sighandler); - int result = PyRun_SimpleFile(fp, python_file); - signal(SIGINT, SIG_DFL); - fclose(fp); - if (result == -1) { - log_error("Error occurred while executing Python script %s\n", python_file); - } - } catch (py::error_already_set const &) { - // Parse and output the exception - std::string perror_str = parse_python_exception(); - signal(SIGINT, SIG_DFL); - log_error("Error in Python: %s\n", perror_str.c_str()); - } -} - -NEXTPNR_NAMESPACE_END - -#endif // NO_PYTHON diff --git a/common/pybindings.h b/common/pybindings.h deleted file mode 100644 index 695441f3..00000000 --- a/common/pybindings.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * 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 COMMON_PYBINDINGS_H -#define COMMON_PYBINDINGS_H - -#include -#include -#include -#include -#include -#include -#include "pycontainers.h" -#include "pywrappers.h" - -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -namespace py = pybind11; - -std::string parse_python_exception(); - -template void python_export_global(const char *name, Tn &x) -{ - try { - py::object obj = py::cast(x, py::return_value_policy::reference); - py::module::import("__main__").attr(name) = obj.ptr(); - } catch (pybind11::error_already_set &) { - // Parse and output the exception - std::string perror_str = parse_python_exception(); - std::cout << "Error in Python: " << perror_str << std::endl; - std::terminate(); - } -}; - -void init_python(const char *executable); - -void deinit_python(); - -void execute_python_file(const char *python_file); - -// Defauld IdString conversions -namespace PythonConversion { - -template <> struct string_converter -{ - inline IdString from_str(Context *ctx, std::string name) { return ctx->id(name); } - - inline std::string to_str(Context *ctx, IdString id) { return id.str(ctx); } -}; - -template <> struct string_converter -{ - inline IdString from_str(Context *ctx, std::string name) { return ctx->id(name); } - - inline std::string to_str(Context *ctx, IdString id) { return id.str(ctx); } -}; - -template <> struct string_converter -{ - IdStringList from_str(Context *ctx, std::string name) { return IdStringList::parse(ctx, name); } - std::string to_str(Context *ctx, const IdStringList &id) { return id.str(ctx); } -}; - -template <> struct string_converter -{ - IdStringList from_str(Context *ctx, std::string name) { return IdStringList::parse(ctx, name); } - std::string to_str(Context *ctx, const IdStringList &id) { return id.str(ctx); } -}; - -} // namespace PythonConversion - -NEXTPNR_NAMESPACE_END - -#endif /* end of include guard: COMMON_PYBINDINGS_HH */ diff --git a/common/pycontainers.h b/common/pycontainers.h deleted file mode 100644 index ff49c34c..00000000 --- a/common/pycontainers.h +++ /dev/null @@ -1,575 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * 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 COMMON_PYCONTAINERS_H -#define COMMON_PYCONTAINERS_H - -#include -#include -#include -#include -#include -#include "nextpnr.h" -#include "pywrappers.h" - -NEXTPNR_NAMESPACE_BEGIN - -namespace py = pybind11; - -inline void KeyError() -{ - PyErr_SetString(PyExc_KeyError, "Key not found"); - throw py::error_already_set(); -} - -/* -A wrapper for a Pythonised nextpnr Iterator. The actual class wrapped is a -pair containing (current, end), wrapped in a ContextualWrapper - -*/ - -template > -struct iterator_wrapper -{ - typedef decltype(*(std::declval())) value_t; - - typedef PythonConversion::ContextualWrapper> wrapped_iter_t; - using return_t = typename value_conv::ret_type; - - static return_t next(wrapped_iter_t &iter) - { - if (iter.base.first != iter.base.second) { - return_t val = value_conv()(iter.ctx, *iter.base.first); - ++iter.base.first; - return val; - } else { - PyErr_SetString(PyExc_StopIteration, "End of range reached"); - throw py::error_already_set(); - } - } - - static void wrap(py::module &m, const char *python_name) - { - py::class_(m, python_name).def("__next__", next, P); - } -}; - -/* -A pair that doesn't automatically become a tuple -*/ -template struct iter_pair -{ - iter_pair(){}; - iter_pair(const Ta &first, const Tb &second) : first(first), second(second){}; - Ta first; - Tb second; -}; - -/* -A wrapper for a nextpnr Range. Ranges should have two functions, begin() -and end() which return iterator-like objects supporting ++, * and != -Full STL iterator semantics are not required, unlike the standard Boost wrappers -*/ - -template > -struct range_wrapper -{ - typedef decltype(std::declval().begin()) iterator_t; - typedef decltype(*(std::declval())) value_t; - typedef typename PythonConversion::ContextualWrapper wrapped_range; - typedef typename PythonConversion::ContextualWrapper> wrapped_pair; - static wrapped_pair iter(wrapped_range &range) - { - return wrapped_pair(range.ctx, std::make_pair(range.base.begin(), range.base.end())); - } - - static std::string repr(wrapped_range &range) - { - PythonConversion::string_converter conv; - bool first = true; - std::stringstream ss; - ss << "["; - for (const auto &item : range.base) { - if (!first) - ss << ", "; - ss << "'" << conv.to_str(range.ctx, item) << "'"; - first = false; - } - ss << "]"; - return ss.str(); - } - - static void wrap(py::module &m, const char *range_name, const char *iter_name) - { - py::class_(m, range_name).def("__iter__", iter).def("__repr__", repr); - iterator_wrapper().wrap(m, iter_name); - } - - typedef iterator_wrapper iter_wrap; -}; - -#define WRAP_RANGE(m, t, conv) \ - range_wrapper().wrap(m, #t "Range", #t "Iterator") - -/* -A wrapper for a vector or similar structure. With support for conversion -*/ - -template > -struct vector_wrapper -{ - typedef decltype(std::declval().begin()) iterator_t; - typedef decltype(*(std::declval())) value_t; - typedef typename PythonConversion::ContextualWrapper wrapped_vector; - typedef typename PythonConversion::ContextualWrapper> wrapped_pair; - using return_t = typename value_conv::ret_type; - static wrapped_pair iter(wrapped_vector &range) - { - return wrapped_pair(range.ctx, std::make_pair(range.base.begin(), range.base.end())); - } - - static std::string repr(wrapped_vector &range) - { - PythonConversion::string_converter conv; - bool first = true; - std::stringstream ss; - ss << "["; - for (const auto &item : range.base) { - if (!first) - ss << ", "; - ss << "'" << conv.to_str(range.ctx, item) << "'"; - first = false; - } - ss << "]"; - return ss.str(); - } - - static int len(wrapped_vector &range) { return range.base.size(); } - - static return_t getitem(wrapped_vector &range, int i) - { - return value_conv()(range.ctx, boost::ref(range.base.at(i))); - } - - static void wrap(py::module &m, const char *range_name, const char *iter_name) - { - py::class_(m, range_name) - .def("__iter__", iter) - .def("__repr__", repr) - .def("__len__", len) - .def("__getitem__", getitem); - - iterator_wrapper().wrap(m, iter_name); - } - - typedef iterator_wrapper iter_wrap; -}; - -#define WRAP_VECTOR(m, t, conv) vector_wrapper().wrap(m, #t, #t "Iterator") - -template > -struct indexed_store_wrapper -{ - typedef decltype(std::declval().begin()) iterator_t; - typedef decltype(*(std::declval())) value_t; - typedef typename PythonConversion::ContextualWrapper wrapped_vector; - typedef typename PythonConversion::ContextualWrapper> wrapped_pair; - using return_t = typename value_conv::ret_type; - static wrapped_pair iter(wrapped_vector &range) - { - return wrapped_pair(range.ctx, std::make_pair(range.base.begin(), range.base.end())); - } - - static std::string repr(wrapped_vector &range) - { - PythonConversion::string_converter conv; - bool first = true; - std::stringstream ss; - ss << "["; - for (const auto &item : range.base) { - if (!first) - ss << ", "; - ss << "'" << conv.to_str(range.ctx, item) << "'"; - first = false; - } - ss << "]"; - return ss.str(); - } - - static int len(wrapped_vector &range) { return range.base.capacity(); } - - static py::object getitem(wrapped_vector &range, int i) - { - store_index> idx(i); - if (!range.base.count(idx)) - throw py::none(); - return py::cast(value_conv()(range.ctx, boost::ref(range.base.at(idx)))); - } - - static void wrap(py::module &m, const char *range_name, const char *iter_name) - { - py::class_(m, range_name) - .def("__iter__", iter) - .def("__repr__", repr) - .def("__len__", len) - .def("__getitem__", getitem); - - iterator_wrapper().wrap(m, iter_name); - } - - typedef iterator_wrapper iter_wrap; -}; - -#define WRAP_INDEXSTORE(m, t, conv) \ - indexed_store_wrapper().wrap(m, #t, #t "Iterator") - -/* -Wrapper for a pair, allows accessing either using C++-style members (.first and -.second) or as a Python iterable and indexable object -*/ -template struct pair_wrapper -{ - typedef std::pair T; - - struct pair_iterator_wrapper - { - static py::object next(iter_pair &iter) - { - if (iter.second == 0) { - iter.second++; - return py::cast(iter.first.first); - } else if (iter.second == 1) { - iter.second++; - return py::cast(iter.first.second); - } else { - PyErr_SetString(PyExc_StopIteration, "End of range reached"); - throw py::error_already_set(); - } - } - - static void wrap(py::module &m, const char *python_name) - { - py::class_>(m, python_name).def("__next__", next); - } - }; - - static py::object get(T &x, int i) - { - if ((i >= 2) || (i < 0)) - KeyError(); - return (i == 1) ? py::object(x.second) : py::object(x.first); - } - - static void set(T &x, int i, py::object val) - { - if ((i >= 2) || (i < 0)) - KeyError(); - if (i == 0) - x.first = val.cast(); - if (i == 1) - x.second = val.cast(); - } - - static int len(T &x) { return 2; } - - static iter_pair iter(T &x) { return iter_pair(boost::ref(x), 0); }; - - static void wrap(py::module &m, const char *pair_name, const char *iter_name) - { - pair_iterator_wrapper::wrap(m, iter_name); - py::class_(m, pair_name) - .def("__iter__", iter) - .def("__len__", len) - .def("__getitem__", get) - .def("__setitem__", set, py::keep_alive<1, 2>()) - .def_readwrite("first", &T::first) - .def_readwrite("second", &T::second); - } -}; - -/* -Special case of above for map key/values - */ -template struct map_pair_wrapper -{ - typedef std::pair T; - typedef PythonConversion::ContextualWrapper wrapped_pair; - typedef typename T::second_type V; - - struct pair_iterator_wrapper - { - static py::object next(iter_pair &iter) - { - if (iter.second == 0) { - iter.second++; - return py::cast(PythonConversion::string_converter().to_str( - iter.first.ctx, iter.first.base.first)); - } else if (iter.second == 1) { - iter.second++; - return py::cast(value_conv()(iter.first.ctx, iter.first.base.second)); - } else { - PyErr_SetString(PyExc_StopIteration, "End of range reached"); - throw py::error_already_set(); - } - } - - static void wrap(py::module &m, const char *python_name) - { - py::class_>(m, python_name).def("__next__", next); - } - }; - - static py::object get(wrapped_pair &x, int i) - { - if ((i >= 2) || (i < 0)) - KeyError(); - return (i == 1) ? py::cast(value_conv()(x.ctx, x.base.second)) - : py::cast(PythonConversion::string_converter().to_str(x.ctx, - x.base.first)); - } - - static int len(wrapped_pair &x) { return 2; } - - static iter_pair iter(wrapped_pair &x) - { - return iter_pair(boost::ref(x), 0); - }; - - static std::string first_getter(wrapped_pair &t) - { - return PythonConversion::string_converter().to_str(t.ctx, t.base.first); - } - - static typename value_conv::ret_type second_getter(wrapped_pair &t) { return value_conv()(t.ctx, t.base.second); } - - static void wrap(py::module &m, const char *pair_name, const char *iter_name) - { - pair_iterator_wrapper::wrap(m, iter_name); - py::class_(m, pair_name) - .def("__iter__", iter) - .def("__len__", len) - .def("__getitem__", get) - .def_property_readonly("first", first_getter) - .def_property_readonly("second", second_getter); - } -}; - -/* -Wrapper for a map, either an unordered_map, regular map or dict - */ - -template struct map_wrapper -{ - typedef typename std::remove_cv::type>::type K; - typedef typename T::mapped_type V; - typedef typename value_conv::ret_type wrapped_V; - typedef typename T::value_type KV; - typedef typename PythonConversion::ContextualWrapper wrapped_map; - - static wrapped_V get(wrapped_map &x, std::string const &i) - { - K k = PythonConversion::string_converter().from_str(x.ctx, i); - if (x.base.find(k) != x.base.end()) - return value_conv()(x.ctx, x.base.at(k)); - KeyError(); - - // Should be unreachable, but prevent control may reach end of non-void - throw std::runtime_error("unreachable"); - } - - static void set(wrapped_map &x, std::string const &i, V const &v) - { - x.base[PythonConversion::string_converter().from_str(x.ctx, i)] = v; - } - - static size_t len(wrapped_map &x) { return x.base.size(); } - - static void del(T const &x, std::string const &i) - { - K k = PythonConversion::string_converter().from_str(x.ctx, i); - if (x.base.find(k) != x.base.end()) - x.base.erase(k); - else - KeyError(); - } - - static bool contains(wrapped_map &x, std::string const &i) - { - K k = PythonConversion::string_converter().from_str(x.ctx, i); - return x.base.count(k); - } - - static void wrap(py::module &m, const char *map_name, const char *kv_name, const char *kv_iter_name, - const char *iter_name) - { - map_pair_wrapper::wrap(m, kv_name, kv_iter_name); - typedef range_wrapper> rw; - typename rw::iter_wrap().wrap(m, iter_name); - py::class_(m, map_name) - .def("__iter__", rw::iter) - .def("__len__", len) - .def("__contains__", contains) - .def("__getitem__", get) - .def("__setitem__", set, py::keep_alive<1, 2>()); - } -}; - -/* -Special case of above for map key/values where value is a unique_ptr - */ -template struct map_pair_wrapper_uptr -{ - typedef std::pair T; - typedef PythonConversion::ContextualWrapper wrapped_pair; - typedef typename T::second_type::element_type V; - - struct pair_iterator_wrapper - { - static py::object next(iter_pair &iter) - { - if (iter.second == 0) { - iter.second++; - return py::cast(PythonConversion::string_converter().to_str( - iter.first.ctx, iter.first.base.first)); - } else if (iter.second == 1) { - iter.second++; - return py::cast( - PythonConversion::ContextualWrapper(iter.first.ctx, *iter.first.base.second.get())); - } else { - PyErr_SetString(PyExc_StopIteration, "End of range reached"); - throw py::error_already_set(); - } - } - - static void wrap(py::module &m, const char *python_name) - { - py::class_>(m, python_name).def("__next__", next); - } - }; - - static py::object get(wrapped_pair &x, int i) - { - if ((i >= 2) || (i < 0)) - KeyError(); - return (i == 1) ? py::cast(PythonConversion::ContextualWrapper(x.ctx, *x.base.second.get())) - : py::cast(PythonConversion::string_converter().to_str(x.ctx, - x.base.first)); - } - - static int len(wrapped_pair &x) { return 2; } - - static iter_pair iter(wrapped_pair &x) - { - return iter_pair(boost::ref(x), 0); - }; - - static std::string first_getter(wrapped_pair &t) - { - return PythonConversion::string_converter().to_str(t.ctx, t.base.first); - } - - static PythonConversion::ContextualWrapper second_getter(wrapped_pair &t) - { - return PythonConversion::ContextualWrapper(t.ctx, *t.base.second.get()); - } - - static void wrap(py::module &m, const char *pair_name, const char *iter_name) - { - pair_iterator_wrapper::wrap(m, iter_name); - py::class_(m, pair_name) - .def("__iter__", iter) - .def("__len__", len) - .def("__getitem__", get) - .def_property_readonly("first", first_getter) - .def_property_readonly("second", second_getter); - } -}; - -/* -Wrapper for a map, either an unordered_map, regular map or dict - */ - -template struct map_wrapper_uptr -{ - typedef typename std::remove_cv::type>::type K; - typedef typename T::mapped_type::pointer V; - typedef typename T::mapped_type::element_type &Vr; - typedef typename T::value_type KV; - typedef typename PythonConversion::ContextualWrapper wrapped_map; - - static PythonConversion::ContextualWrapper get(wrapped_map &x, std::string const &i) - { - K k = PythonConversion::string_converter().from_str(x.ctx, i); - if (x.base.find(k) != x.base.end()) - return PythonConversion::ContextualWrapper(x.ctx, *x.base.at(k).get()); - KeyError(); - - // Should be unreachable, but prevent control may reach end of non-void - throw std::runtime_error("unreachable"); - } - - static void set(wrapped_map &x, std::string const &i, V const &v) - { - x.base[PythonConversion::string_converter().from_str(x.ctx, i)] = typename T::mapped_type(v); - } - - static size_t len(wrapped_map &x) { return x.base.size(); } - - static void del(T const &x, std::string const &i) - { - K k = PythonConversion::string_converter().from_str(x.ctx, i); - if (x.base.find(k) != x.base.end()) - x.base.erase(k); - else - KeyError(); - } - - static bool contains(wrapped_map &x, std::string const &i) - { - K k = PythonConversion::string_converter().from_str(x.ctx, i); - return x.base.count(k); - } - - static void wrap(py::module &m, const char *map_name, const char *kv_name, const char *kv_iter_name, - const char *iter_name) - { - map_pair_wrapper_uptr::wrap(m, kv_name, kv_iter_name); - typedef range_wrapper> rw; - typename rw::iter_wrap().wrap(m, iter_name); - py::class_(m, map_name) - .def("__iter__", rw::iter) - .def("__len__", len) - .def("__contains__", contains) - .def("__getitem__", get) - .def("__setitem__", set, py::keep_alive<1, 2>()); - } -}; - -#define WRAP_MAP(m, t, conv, name) \ - map_wrapper().wrap(m, #name, #name "KeyValue", #name "KeyValueIter", #name "Iterator") -#define WRAP_MAP_UPTR(m, t, name) \ - map_wrapper_uptr().wrap(m, #name, #name "KeyValue", #name "KeyValueIter", #name "Iterator") - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/pywrappers.h b/common/pywrappers.h deleted file mode 100644 index 60ef65be..00000000 --- a/common/pywrappers.h +++ /dev/null @@ -1,463 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * 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 PYWRAPPERS_H -#define PYWRAPPERS_H - -#include -#include -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -namespace py = pybind11; - -namespace PythonConversion { -template struct ContextualWrapper -{ - Context *ctx; - T base; - - inline ContextualWrapper(Context *c, T x) : ctx(c), base(x){}; - - inline operator T() { return base; }; - typedef T base_type; -}; - -template struct WrapIfNotContext -{ - typedef ContextualWrapper maybe_wrapped_t; -}; - -template <> struct WrapIfNotContext -{ - typedef Context maybe_wrapped_t; -}; - -template inline Context *get_ctx(typename WrapIfNotContext::maybe_wrapped_t &wrp_ctx) -{ - return wrp_ctx.ctx; -} - -template <> inline Context *get_ctx(WrapIfNotContext::maybe_wrapped_t &unwrp_ctx) -{ - return &unwrp_ctx; -} - -template inline T &get_base(typename WrapIfNotContext::maybe_wrapped_t &wrp_ctx) -{ - return wrp_ctx.base; -} - -template <> inline Context &get_base(WrapIfNotContext::maybe_wrapped_t &unwrp_ctx) -{ - return unwrp_ctx; -} - -template ContextualWrapper wrap_ctx(Context *ctx, T x) { return ContextualWrapper(ctx, x); } - -// Dummy class, to be implemented by users -template struct string_converter; - -class bad_wrap -{ -}; - -// Action options -template struct pass_through -{ - inline T operator()(Context *ctx, T x) { return x; } - - using ret_type = T; - using arg_type = T; -}; - -template struct wrap_context -{ - inline ContextualWrapper operator()(Context *ctx, T x) { return ContextualWrapper(ctx, x); } - - using arg_type = T; - using ret_type = ContextualWrapper; -}; - -template struct unwrap_context -{ - inline T operator()(Context *ctx, ContextualWrapper x) { return x.base; } - - using ret_type = T; - using arg_type = ContextualWrapper; -}; - -template struct conv_from_str -{ - inline T operator()(Context *ctx, std::string x) { return string_converter().from_str(ctx, x); } - - using ret_type = T; - using arg_type = std::string; -}; - -template struct conv_to_str -{ - inline std::string operator()(Context *ctx, T x) { return string_converter().to_str(ctx, x); } - - using ret_type = std::string; - using arg_type = T; -}; - -template struct deref_and_wrap -{ - inline ContextualWrapper operator()(Context *ctx, T *x) - { - if (x == nullptr) - throw bad_wrap(); - return ContextualWrapper(ctx, *x); - } - - using arg_type = T *; - using ret_type = ContextualWrapper; -}; - -template struct addr_and_unwrap -{ - inline T *operator()(Context *ctx, ContextualWrapper x) { return &(x.base); } - - using arg_type = ContextualWrapper; - using ret_type = T *; -}; - -// Function wrapper -// Zero parameters, one return -template struct fn_wrapper_0a -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - using conv_result_type = typename rv_conv::ret_type; - - static py::object wrapped_fn(class_type &cls) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - try { - return py::cast(rv_conv()(ctx, (base.*fn)())); - } catch (bad_wrap &) { - return py::none(); - } - } - - template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } -}; - -// One parameter, one return -template struct fn_wrapper_1a -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - using conv_result_type = typename rv_conv::ret_type; - using conv_arg1_type = typename arg1_conv::arg_type; - - static py::object wrapped_fn(class_type &cls, conv_arg1_type arg1) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - try { - return py::cast(rv_conv()(ctx, (base.*fn)(arg1_conv()(ctx, arg1)))); - } catch (bad_wrap &) { - return py::none(); - } - } - - template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } -}; - -// Two parameters, one return -template -struct fn_wrapper_2a -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - using conv_result_type = typename rv_conv::ret_type; - using conv_arg1_type = typename arg1_conv::arg_type; - using conv_arg2_type = typename arg2_conv::arg_type; - - static py::object wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - try { - return py::cast(rv_conv()(ctx, (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2)))); - } catch (bad_wrap &) { - return py::none(); - } - } - - template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } -}; - -// Three parameters, one return -template -struct fn_wrapper_3a -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - using conv_result_type = typename rv_conv::ret_type; - using conv_arg1_type = typename arg1_conv::arg_type; - using conv_arg2_type = typename arg2_conv::arg_type; - using conv_arg3_type = typename arg3_conv::arg_type; - - static py::object wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - try { - return py::cast( - rv_conv()(ctx, (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3)))); - } catch (bad_wrap &) { - return py::none(); - } - } - - template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } -}; - -// Zero parameters void -template struct fn_wrapper_0a_v -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - - static void wrapped_fn(class_type &cls) - { - Class &base = get_base(cls); - return (base.*fn)(); - } - - template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } -}; - -// One parameter, void -template struct fn_wrapper_1a_v -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - using conv_arg1_type = typename arg1_conv::arg_type; - - static void wrapped_fn(class_type &cls, conv_arg1_type arg1) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - (base.*fn)(arg1_conv()(ctx, arg1)); - } - - template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } - - template - static void def_wrap(WrapCls cls_, const char *name, Ta a = py::arg("arg1")) - { - cls_.def(name, wrapped_fn, a); - } -}; - -// Two parameters, no return -template struct fn_wrapper_2a_v -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - using conv_arg1_type = typename arg1_conv::arg_type; - using conv_arg2_type = typename arg2_conv::arg_type; - - static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2)); - } - - template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } - - template static void def_wrap(WrapCls cls_, const char *name, Ta... a) - { - cls_.def(name, wrapped_fn, a...); - } -}; - -// Three parameters, no return -template -struct fn_wrapper_3a_v -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - using conv_arg1_type = typename arg1_conv::arg_type; - using conv_arg2_type = typename arg2_conv::arg_type; - using conv_arg3_type = typename arg3_conv::arg_type; - - static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3)); - } - - template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } - - template static void def_wrap(WrapCls cls_, const char *name, Ta... a) - { - cls_.def(name, wrapped_fn, a...); - } -}; - -// Four parameters, no return -template -struct fn_wrapper_4a_v -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - using conv_arg1_type = typename arg1_conv::arg_type; - using conv_arg2_type = typename arg2_conv::arg_type; - using conv_arg3_type = typename arg3_conv::arg_type; - using conv_arg4_type = typename arg4_conv::arg_type; - - static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3, - conv_arg4_type arg4) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), arg4_conv()(ctx, arg4)); - } - - template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } - - template static void def_wrap(WrapCls cls_, const char *name, Ta... a) - { - cls_.def(name, wrapped_fn, a...); - } -}; - -// Five parameters, no return -template -struct fn_wrapper_5a_v -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - using conv_arg1_type = typename arg1_conv::arg_type; - using conv_arg2_type = typename arg2_conv::arg_type; - using conv_arg3_type = typename arg3_conv::arg_type; - using conv_arg4_type = typename arg4_conv::arg_type; - using conv_arg5_type = typename arg5_conv::arg_type; - - static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3, - conv_arg4_type arg4, conv_arg5_type arg5) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), arg4_conv()(ctx, arg4), - arg5_conv()(ctx, arg5)); - } - - template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } - - template static void def_wrap(WrapCls cls_, const char *name, Ta... a) - { - cls_.def(name, wrapped_fn, a...); - } -}; - -// Six parameters, no return -template -struct fn_wrapper_6a_v -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - using conv_arg1_type = typename arg1_conv::arg_type; - using conv_arg2_type = typename arg2_conv::arg_type; - using conv_arg3_type = typename arg3_conv::arg_type; - using conv_arg4_type = typename arg4_conv::arg_type; - using conv_arg5_type = typename arg5_conv::arg_type; - using conv_arg6_type = typename arg6_conv::arg_type; - - static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3, - conv_arg4_type arg4, conv_arg5_type arg5, conv_arg6_type arg6) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3), arg4_conv()(ctx, arg4), - arg5_conv()(ctx, arg5), arg6_conv()(ctx, arg6)); - } - - template static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); } - - template static void def_wrap(WrapCls cls_, const char *name, Ta... a) - { - cls_.def(name, wrapped_fn, a...); - } -}; - -// Wrapped getter -template struct readonly_wrapper -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - using conv_val_type = typename v_conv::ret_type; - - static py::object wrapped_getter(class_type &cls) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - try { - return py::cast(v_conv()(ctx, (base.*mem))); - } catch (bad_wrap &) { - return py::none(); - } - } - - template static void def_wrap(WrapCls cls_, const char *name) - { - cls_.def_property_readonly(name, wrapped_getter); - } -}; - -// Wrapped getter/setter -template struct readwrite_wrapper -{ - using class_type = typename WrapIfNotContext::maybe_wrapped_t; - using conv_val_type = typename get_conv::ret_type; - - static py::object wrapped_getter(class_type &cls) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - try { - return py::cast(get_conv()(ctx, (base.*mem))); - } catch (bad_wrap &) { - return py::none(); - } - } - - using conv_arg_type = typename set_conv::arg_type; - - static void wrapped_setter(class_type &cls, conv_arg_type val) - { - Context *ctx = get_ctx(cls); - Class &base = get_base(cls); - (base.*mem) = set_conv()(ctx, val); - } - - template static void def_wrap(WrapCls cls_, const char *name) - { - cls_.def_property(name, wrapped_getter, wrapped_setter); - } -}; - -} // namespace PythonConversion - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/relptr.h b/common/relptr.h deleted file mode 100644 index f0f45b7d..00000000 --- a/common/relptr.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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 RELPTR_H -#define RELPTR_H - -#include - -#include "nextpnr_assertions.h" -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -template struct RelPtr -{ - int32_t offset; - - const T *get() const { return reinterpret_cast(reinterpret_cast(this) + offset); } - - const T &operator[](std::size_t index) const { return get()[index]; } - - const T &operator*() const { return *(get()); } - - const T *operator->() const { return get(); } - - RelPtr(const RelPtr &) = delete; - RelPtr &operator=(const RelPtr &) = delete; -}; - -NPNR_PACKED_STRUCT(template struct RelSlice { - int32_t offset; - uint32_t length; - - const T *get() const { return reinterpret_cast(reinterpret_cast(this) + offset); } - - const T &operator[](std::size_t index) const - { - NPNR_ASSERT(index < length); - return get()[index]; - } - - const T *begin() const { return get(); } - const T *end() const { return get() + length; } - - size_t size() const { return length; } - ptrdiff_t ssize() const { return length; } - - const T &operator*() const { return *(get()); } - - const T *operator->() const { return get(); } - - RelSlice(const RelSlice &) = delete; - RelSlice &operator=(const RelSlice &) = delete; -}); - -NEXTPNR_NAMESPACE_END - -#endif /* RELPTR_H */ diff --git a/common/report.cc b/common/report.cc deleted file mode 100644 index 98ff14fb..00000000 --- a/common/report.cc +++ /dev/null @@ -1,259 +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 "json11.hpp" -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -using namespace json11; - -namespace { -dict> get_utilization(const Context *ctx) -{ - // Sort by Bel type - dict> result; - for (auto &cell : ctx->cells) { - result[ctx->getBelBucketName(ctx->getBelBucketForCellType(cell.second.get()->type))].first++; - } - for (auto bel : ctx->getBels()) { - if (!ctx->getBelHidden(bel)) { - result[ctx->getBelBucketName(ctx->getBelBucketForBel(bel))].second++; - } - } - return result; -} -} // namespace - -static std::string clock_event_name(const Context *ctx, const ClockEvent &e) -{ - std::string value; - if (e.clock == ctx->id("$async$")) - value = std::string(""); - else - value = (e.edge == FALLING_EDGE ? std::string("negedge ") : std::string("posedge ")) + e.clock.str(ctx); - return value; -}; - -static Json::array report_critical_paths(const Context *ctx) -{ - - auto report_critical_path = [ctx](const CriticalPath &report) { - Json::array pathJson; - - for (const auto &segment : report.segments) { - - const auto &driver = ctx->cells.at(segment.from.first); - const auto &sink = ctx->cells.at(segment.to.first); - - auto fromLoc = ctx->getBelLocation(driver->bel); - auto toLoc = ctx->getBelLocation(sink->bel); - - auto fromJson = Json::object({{"cell", segment.from.first.c_str(ctx)}, - {"port", segment.from.second.c_str(ctx)}, - {"loc", Json::array({fromLoc.x, fromLoc.y})}}); - - auto toJson = Json::object({{"cell", segment.to.first.c_str(ctx)}, - {"port", segment.to.second.c_str(ctx)}, - {"loc", Json::array({toLoc.x, toLoc.y})}}); - - auto segmentJson = Json::object({ - {"delay", ctx->getDelayNS(segment.delay)}, - {"from", fromJson}, - {"to", toJson}, - }); - - if (segment.type == CriticalPath::Segment::Type::CLK_TO_Q) { - segmentJson["type"] = "clk-to-q"; - } else if (segment.type == CriticalPath::Segment::Type::SOURCE) { - segmentJson["type"] = "source"; - } else if (segment.type == CriticalPath::Segment::Type::LOGIC) { - segmentJson["type"] = "logic"; - } else if (segment.type == CriticalPath::Segment::Type::SETUP) { - segmentJson["type"] = "setup"; - } else if (segment.type == CriticalPath::Segment::Type::ROUTING) { - segmentJson["type"] = "routing"; - segmentJson["net"] = segment.net.c_str(ctx); - segmentJson["budget"] = ctx->getDelayNS(segment.budget); - } - - pathJson.push_back(segmentJson); - } - - return pathJson; - }; - - auto critPathsJson = Json::array(); - - // Critical paths - for (auto &report : ctx->timing_result.clock_paths) { - - critPathsJson.push_back(Json::object({{"from", clock_event_name(ctx, report.second.clock_pair.start)}, - {"to", clock_event_name(ctx, report.second.clock_pair.end)}, - {"path", report_critical_path(report.second)}})); - } - - // Cross-domain paths - for (auto &report : ctx->timing_result.xclock_paths) { - critPathsJson.push_back(Json::object({{"from", clock_event_name(ctx, report.clock_pair.start)}, - {"to", clock_event_name(ctx, report.clock_pair.end)}, - {"path", report_critical_path(report)}})); - } - - return critPathsJson; -} - -static Json::array report_detailed_net_timings(const Context *ctx) -{ - auto detailedNetTimingsJson = Json::array(); - - // Detailed per-net timing analysis - for (const auto &it : ctx->timing_result.detailed_net_timings) { - - const NetInfo *net = ctx->nets.at(it.first).get(); - ClockEvent start = it.second[0].clock_pair.start; - - Json::array endpointsJson; - for (const auto &sink_timing : it.second) { - - // FIXME: Is it possible that there are multiple different start - // events for a single net? It has a single driver - NPNR_ASSERT(sink_timing.clock_pair.start == start); - - auto endpointJson = Json::object({{"cell", sink_timing.cell_port.first.c_str(ctx)}, - {"port", sink_timing.cell_port.second.c_str(ctx)}, - {"event", clock_event_name(ctx, sink_timing.clock_pair.end)}, - {"delay", ctx->getDelayNS(sink_timing.delay)}, - {"budget", ctx->getDelayNS(sink_timing.budget)}}); - endpointsJson.push_back(endpointJson); - } - - auto netTimingJson = Json::object({{"net", net->name.c_str(ctx)}, - {"driver", net->driver.cell->name.c_str(ctx)}, - {"port", net->driver.port.c_str(ctx)}, - {"event", clock_event_name(ctx, start)}, - {"endpoints", endpointsJson}}); - - detailedNetTimingsJson.push_back(netTimingJson); - } - - return detailedNetTimingsJson; -} - -/* -Report JSON structure: - -{ - "utilization": { - : { - "available": , - "used": - }, - ... - }, - "fmax" { - : { - "achieved": , - "constraint": - }, - ... - }, - "critical_paths": [ - { - "from": , - "to": , - "path": [ - { - "from": { - "cell": - "port": - "loc": [ - , - - ] - }, - "to": { - "cell": - "port": - "loc": [ - , - - ] - }, - "type": , - "net": , - "delay": , - "budget": , - } - ... - ] - }, - ... - ], - "detailed_net_timings": [ - { - "driver": , - "port": , - "event": , - "net": , - "endpoints": [ - { - "cell": , - "port": , - "event": , - "delay": , - "budget": , - } - ... - ] - } - ... - ] -} -*/ - -void Context::writeReport(std::ostream &out) const -{ - auto util = get_utilization(this); - dict util_json; - for (const auto &kv : util) { - util_json[kv.first.str(this)] = Json::object{ - {"used", kv.second.first}, - {"available", kv.second.second}, - }; - } - dict fmax_json; - for (const auto &kv : timing_result.clock_fmax) { - fmax_json[kv.first.str(this)] = Json::object{ - {"achieved", kv.second.achieved}, - {"constraint", kv.second.constraint}, - }; - } - - Json::object jsonRoot{ - {"utilization", util_json}, {"fmax", fmax_json}, {"critical_paths", report_critical_paths(this)}}; - - if (detailed_timing_report) { - jsonRoot["detailed_net_timings"] = report_detailed_net_timings(this); - } - - out << Json(jsonRoot).dump() << std::endl; -} - -NEXTPNR_NAMESPACE_END diff --git a/common/route/router1.cc b/common/route/router1.cc new file mode 100644 index 00000000..98132116 --- /dev/null +++ b/common/route/router1.cc @@ -0,0 +1,1175 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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 +#include + +#include "log.h" +#include "router1.h" +#include "scope_lock.h" +#include "timing.h" + +namespace { + +USING_NEXTPNR_NAMESPACE + +struct arc_key +{ + NetInfo *net_info; + // logical user cell port index + store_index user_idx; + // physical index into cell->bel pin mapping (usually 0) + unsigned phys_idx; + + bool operator==(const arc_key &other) const + { + return (net_info == other.net_info) && (user_idx == other.user_idx) && (phys_idx == other.phys_idx); + } + bool operator<(const arc_key &other) const + { + return net_info == other.net_info + ? (user_idx == other.user_idx ? phys_idx < other.phys_idx : user_idx < other.user_idx) + : net_info->name < other.net_info->name; + } + + unsigned int hash() const + { + std::size_t seed = std::hash()(net_info); + seed ^= user_idx.hash() + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= std::hash()(phys_idx) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } +}; + +struct arc_entry +{ + arc_key arc; + delay_t pri; + int randtag = 0; + + struct Less + { + bool operator()(const arc_entry &lhs, const arc_entry &rhs) const noexcept + { + if (lhs.pri != rhs.pri) + return lhs.pri < rhs.pri; + return lhs.randtag < rhs.randtag; + } + }; +}; + +struct QueuedWire +{ + WireId wire; + PipId pip; + + delay_t delay = 0, penalty = 0, bonus = 0, togo = 0; + int randtag = 0; + + struct Greater + { + bool operator()(const QueuedWire &lhs, const QueuedWire &rhs) const noexcept + { + delay_t l = lhs.delay + lhs.penalty + lhs.togo; + delay_t r = rhs.delay + rhs.penalty + rhs.togo; + NPNR_ASSERT(l >= 0); + NPNR_ASSERT(r >= 0); + l -= lhs.bonus; + r -= rhs.bonus; + return l == r ? lhs.randtag > rhs.randtag : l > r; + } + }; +}; + +struct Router1 +{ + Context *ctx; + const Router1Cfg &cfg; + + std::priority_queue, arc_entry::Less> arc_queue; + dict> wire_to_arcs; + dict> arc_to_wires; + pool queued_arcs; + + std::priority_queue, QueuedWire::Greater> queue; + + dict wireScores; + dict netScores; + + int arcs_with_ripup = 0; + int arcs_without_ripup = 0; + bool ripup_flag; + + TimingAnalyser tmg; + + bool timing_driven = true; + + Router1(Context *ctx, const Router1Cfg &cfg) : ctx(ctx), cfg(cfg), tmg(ctx) + { + timing_driven = ctx->setting("timing_driven"); + tmg.setup(); + tmg.run(); + } + + void arc_queue_insert(const arc_key &arc, WireId src_wire, WireId dst_wire) + { + if (queued_arcs.count(arc)) + return; + + delay_t pri = ctx->estimateDelay(src_wire, dst_wire) * + (100 * tmg.get_criticality(CellPortKey(arc.net_info->users.at(arc.user_idx)))); + + arc_entry entry; + entry.arc = arc; + entry.pri = pri; + entry.randtag = ctx->rng(); + +#if 0 + if (ctx->debug) + log("[arc_queue_insert] %s (%d) %s %s [%d %d]\n", ctx->nameOf(entry.arc.net_info), entry.arc.user_idx, + ctx->nameOfWire(src_wire), ctx->nameOfWire(dst_wire), (int)entry.pri, entry.randtag); +#endif + + arc_queue.push(entry); + queued_arcs.insert(arc); + } + + void arc_queue_insert(const arc_key &arc) + { + if (queued_arcs.count(arc)) + return; + + NetInfo *net_info = arc.net_info; + auto user_idx = arc.user_idx; + unsigned phys_idx = arc.phys_idx; + + auto src_wire = ctx->getNetinfoSourceWire(net_info); + auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx], phys_idx); + + arc_queue_insert(arc, src_wire, dst_wire); + } + + arc_key arc_queue_pop() + { + arc_entry entry = arc_queue.top(); + +#if 0 + if (ctx->debug) + log("[arc_queue_pop] %s (%d) [%d %d]\n", ctx->nameOf(entry.arc.net_info), entry.arc.user_idx, + (int)entry.pri, entry.randtag); +#endif + + arc_queue.pop(); + queued_arcs.erase(entry.arc); + return entry.arc; + } + + void ripup_net(NetInfo *net) + { + if (ctx->debug) + log(" ripup net %s\n", ctx->nameOf(net)); + + netScores[net]++; + + std::vector wires; + for (auto &it : net->wires) + wires.push_back(it.first); + + ctx->sorted_shuffle(wires); + + for (WireId w : wires) { + std::vector arcs; + for (auto &it : wire_to_arcs[w]) { + arc_to_wires[it].erase(w); + arcs.push_back(it); + } + wire_to_arcs[w].clear(); + + ctx->sorted_shuffle(arcs); + + for (auto &it : arcs) + arc_queue_insert(it); + + if (ctx->debug) + log(" unbind wire %s\n", ctx->nameOfWire(w)); + + ctx->unbindWire(w); + wireScores[w]++; + } + + ripup_flag = true; + } + + void ripup_wire(WireId wire, int extra_indent = 0) + { + if (ctx->debug) + log(" ripup wire %s\n", ctx->nameOfWire(wire)); + + WireId w = ctx->getConflictingWireWire(wire); + + if (w == WireId()) { + NetInfo *n = ctx->getConflictingWireNet(wire); + if (n != nullptr) + ripup_net(n); + } else { + std::vector arcs; + for (auto &it : wire_to_arcs[w]) { + arc_to_wires[it].erase(w); + arcs.push_back(it); + } + wire_to_arcs[w].clear(); + + ctx->sorted_shuffle(arcs); + + for (auto &it : arcs) + arc_queue_insert(it); + + if (ctx->debug) + log(" unbind wire %s\n", ctx->nameOfWire(w)); + + ctx->unbindWire(w); + wireScores[w]++; + } + + ripup_flag = true; + } + + void ripup_pip(PipId pip) + { + if (ctx->debug) + log(" ripup pip %s\n", ctx->nameOfPip(pip)); + + WireId w = ctx->getConflictingPipWire(pip); + + if (w == WireId()) { + NetInfo *n = ctx->getConflictingPipNet(pip); + if (n != nullptr) + ripup_net(n); + } else { + std::vector arcs; + for (auto &it : wire_to_arcs[w]) { + arc_to_wires[it].erase(w); + arcs.push_back(it); + } + wire_to_arcs[w].clear(); + + ctx->sorted_shuffle(arcs); + + for (auto &it : arcs) + arc_queue_insert(it); + + if (ctx->debug) + log(" unbind wire %s\n", ctx->nameOfWire(w)); + + ctx->unbindWire(w); + wireScores[w]++; + } + + ripup_flag = true; + } + + bool skip_net(NetInfo *net_info) + { +#ifdef ARCH_ECP5 + // ECP5 global nets currently appear part-unrouted due to arch database limitations + // Don't touch them in the router + if (net_info->is_global) + return true; +#endif + if (net_info->driver.cell == nullptr) + return true; + + return false; + } + + void check() + { + pool valid_arcs; + + for (auto &net_it : ctx->nets) { + NetInfo *net_info = net_it.second.get(); + pool valid_wires_for_net; + + if (skip_net(net_info)) + continue; + +#if 0 + if (ctx->debug) + log("[check] net: %s\n", ctx->nameOf(net_info)); +#endif + + auto src_wire = ctx->getNetinfoSourceWire(net_info); + log_assert(src_wire != WireId()); + + for (auto user : net_info->users.enumerate()) { + unsigned phys_idx = 0; + for (auto dst_wire : ctx->getNetinfoSinkWires(net_info, user.value)) { + log_assert(dst_wire != WireId()); + + arc_key arc; + arc.net_info = net_info; + arc.user_idx = user.index; + arc.phys_idx = phys_idx++; + valid_arcs.insert(arc); +#if 0 + if (ctx->debug) + log("[check] arc: %s %s\n", ctx->nameOfWire(src_wire), ctx->nameOfWire(dst_wire)); +#endif + + for (WireId wire : arc_to_wires[arc]) { +#if 0 + if (ctx->debug) + log("[check] wire: %s\n", ctx->nameOfWire(wire)); +#endif + valid_wires_for_net.insert(wire); + log_assert(wire_to_arcs[wire].count(arc)); + log_assert(net_info->wires.count(wire)); + } + } + } + + for (auto &it : net_info->wires) { + WireId w = it.first; + log_assert(valid_wires_for_net.count(w)); + } + } + + for (auto &it : wire_to_arcs) { + for (auto &arc : it.second) + log_assert(valid_arcs.count(arc)); + } + + for (auto &it : arc_to_wires) { + log_assert(valid_arcs.count(it.first)); + } + } + + void setup() + { + dict src_to_net; + dict dst_to_arc; + + std::vector net_names; + for (auto &net_it : ctx->nets) + net_names.push_back(net_it.first); + + ctx->sorted_shuffle(net_names); + + for (IdString net_name : net_names) { + NetInfo *net_info = ctx->nets.at(net_name).get(); + + if (skip_net(net_info)) + continue; + + auto src_wire = ctx->getNetinfoSourceWire(net_info); + + if (src_wire == WireId()) + log_error("No wire found for port %s on source cell %s.\n", ctx->nameOf(net_info->driver.port), + ctx->nameOf(net_info->driver.cell)); + + if (src_to_net.count(src_wire)) + log_error("Found two nets with same source wire %s: %s vs %s\n", ctx->nameOfWire(src_wire), + ctx->nameOf(net_info), ctx->nameOf(src_to_net.at(src_wire))); + + if (dst_to_arc.count(src_wire)) + log_error("Wire %s is used as source and sink in different nets: %s vs %s (%d)\n", + ctx->nameOfWire(src_wire), ctx->nameOf(net_info), + ctx->nameOf(dst_to_arc.at(src_wire).net_info), dst_to_arc.at(src_wire).user_idx.idx()); + + for (auto user : net_info->users.enumerate()) { + unsigned phys_idx = 0; + for (auto dst_wire : ctx->getNetinfoSinkWires(net_info, user.value)) { + arc_key arc; + arc.net_info = net_info; + arc.user_idx = user.index; + arc.phys_idx = phys_idx++; + + if (dst_to_arc.count(dst_wire)) { + if (dst_to_arc.at(dst_wire).net_info == net_info) + continue; + log_error("Found two arcs with same sink wire %s: %s (%d) vs %s (%d)\n", + ctx->nameOfWire(dst_wire), ctx->nameOf(net_info), user.index.idx(), + ctx->nameOf(dst_to_arc.at(dst_wire).net_info), + dst_to_arc.at(dst_wire).user_idx.idx()); + } + + if (src_to_net.count(dst_wire)) + log_error("Wire %s is used as source and sink in different nets: %s vs %s (%d)\n", + ctx->nameOfWire(dst_wire), ctx->nameOf(src_to_net.at(dst_wire)), + ctx->nameOf(net_info), user.index.idx()); + + dst_to_arc[dst_wire] = arc; + + if (net_info->wires.count(dst_wire) == 0) { + arc_queue_insert(arc, src_wire, dst_wire); + continue; + } + + WireId cursor = dst_wire; + wire_to_arcs[cursor].insert(arc); + arc_to_wires[arc].insert(cursor); + + while (src_wire != cursor) { + auto it = net_info->wires.find(cursor); + if (it == net_info->wires.end()) { + arc_queue_insert(arc, src_wire, dst_wire); + break; + } + + NPNR_ASSERT(it->second.pip != PipId()); + cursor = ctx->getPipSrcWire(it->second.pip); + wire_to_arcs[cursor].insert(arc); + arc_to_wires[arc].insert(cursor); + } + } + // TODO: this matches the situation before supporting multiple cell->bel pins, but do we want to keep + // this invariant? + if (phys_idx == 0) + log_warning("No wires found for port %s on destination cell %s.\n", ctx->nameOf(user.value.port), + ctx->nameOf(user.value.cell)); + } + + src_to_net[src_wire] = net_info; + + std::vector unbind_wires; + + for (auto &it : net_info->wires) + if (it.second.strength < STRENGTH_LOCKED && wire_to_arcs.count(it.first) == 0) + unbind_wires.push_back(it.first); + + for (auto it : unbind_wires) + ctx->unbindWire(it); + } + } + + bool route_arc(const arc_key &arc, bool ripup) + { + + NetInfo *net_info = arc.net_info; + auto user_idx = arc.user_idx; + + auto src_wire = ctx->getNetinfoSourceWire(net_info); + auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx], arc.phys_idx); + ripup_flag = false; + + float crit = tmg.get_criticality(CellPortKey(net_info->users.at(user_idx))); + + if (ctx->debug) { + log("Routing arc %d on net %s (%d arcs total):\n", user_idx.idx(), ctx->nameOf(net_info), + int(net_info->users.capacity())); + log(" source ... %s\n", ctx->nameOfWire(src_wire)); + log(" sink ..... %s\n", ctx->nameOfWire(dst_wire)); + } + + // unbind wires that are currently used exclusively by this arc + + pool old_arc_wires; + old_arc_wires.swap(arc_to_wires[arc]); + + for (WireId wire : old_arc_wires) { + auto &arc_wires = wire_to_arcs.at(wire); + NPNR_ASSERT(arc_wires.count(arc)); + arc_wires.erase(arc); + if (arc_wires.empty()) { + if (ctx->debug) + log(" unbind %s\n", ctx->nameOfWire(wire)); + ctx->unbindWire(wire); + } + } + + // special case + + if (src_wire == dst_wire) { + NetInfo *bound = ctx->getBoundWireNet(src_wire); + if (bound != nullptr) + NPNR_ASSERT(bound == net_info); + else { + ctx->bindWire(src_wire, net_info, STRENGTH_WEAK); + } + arc_to_wires[arc].insert(src_wire); + wire_to_arcs[src_wire].insert(arc); + return true; + } + + // reset wire queue + + if (!queue.empty()) { + std::priority_queue, QueuedWire::Greater> new_queue; + queue.swap(new_queue); + } + dict visited; + + // A* main loop + + int visitCnt = 0; + int maxVisitCnt = INT_MAX; + delay_t best_est = 0; + delay_t best_score = -1; + + { + QueuedWire qw; + qw.wire = src_wire; + qw.pip = PipId(); + qw.delay = ctx->getWireDelay(qw.wire).maxDelay(); + qw.penalty = 0; + qw.bonus = 0; + if (cfg.useEstimate) { + qw.togo = ctx->estimateDelay(qw.wire, dst_wire); + best_est = qw.delay + qw.togo; + } + qw.randtag = ctx->rng(); + + queue.push(qw); + visited[qw.wire] = qw; + } + + while (visitCnt++ < maxVisitCnt && !queue.empty()) { + QueuedWire qw = queue.top(); + queue.pop(); + + for (auto pip : ctx->getPipsDownhill(qw.wire)) { + delay_t next_delay = qw.delay + ctx->getPipDelay(pip).maxDelay(); + delay_t next_penalty = qw.penalty; + delay_t next_bonus = qw.bonus; + delay_t penalty_delta = 0; + + WireId next_wire = ctx->getPipDstWire(pip); + next_delay += ctx->getWireDelay(next_wire).maxDelay(); + + WireId conflictWireWire = WireId(), conflictPipWire = WireId(); + NetInfo *conflictWireNet = nullptr, *conflictPipNet = nullptr; + + if (net_info->wires.count(next_wire) && net_info->wires.at(next_wire).pip == pip) { + next_bonus += cfg.reuseBonus * (1.0 - crit); + } else { + if (!ctx->checkWireAvail(next_wire)) { + if (!ripup) + continue; + conflictWireWire = ctx->getConflictingWireWire(next_wire); + if (conflictWireWire == WireId()) { + conflictWireNet = ctx->getConflictingWireNet(next_wire); + if (conflictWireNet == nullptr) + continue; + else { + if (conflictWireNet->wires.count(next_wire) && + conflictWireNet->wires.at(next_wire).strength > STRENGTH_STRONG) + continue; + } + } else { + NetInfo *conflicting = ctx->getBoundWireNet(conflictWireWire); + if (conflicting != nullptr) { + if (conflicting->wires.count(conflictWireWire) && + conflicting->wires.at(conflictWireWire).strength > STRENGTH_STRONG) + continue; + } + } + } + + if (!ctx->checkPipAvail(pip)) { + if (!ripup) + continue; + conflictPipWire = ctx->getConflictingPipWire(pip); + if (conflictPipWire == WireId()) { + conflictPipNet = ctx->getConflictingPipNet(pip); + if (conflictPipNet == nullptr) + continue; + else { + if (conflictPipNet->wires.count(next_wire) && + conflictPipNet->wires.at(next_wire).strength > STRENGTH_STRONG) + continue; + } + } else { + NetInfo *conflicting = ctx->getBoundWireNet(conflictPipWire); + if (conflicting != nullptr) { + if (conflicting->wires.count(conflictPipWire) && + conflicting->wires.at(conflictPipWire).strength > STRENGTH_STRONG) + continue; + } + } + } + + if (conflictWireNet != nullptr && conflictPipWire != WireId() && + conflictWireNet->wires.count(conflictPipWire)) + conflictPipWire = WireId(); + + if (conflictPipNet != nullptr && conflictWireWire != WireId() && + conflictPipNet->wires.count(conflictWireWire)) + conflictWireWire = WireId(); + + if (conflictWireWire == conflictPipWire) + conflictWireWire = WireId(); + + if (conflictWireNet == conflictPipNet) + conflictWireNet = nullptr; + + if (conflictWireWire != WireId()) { + auto scores_it = wireScores.find(conflictWireWire); + if (scores_it != wireScores.end()) + penalty_delta += scores_it->second * cfg.wireRipupPenalty; + penalty_delta += cfg.wireRipupPenalty; + } + + if (conflictPipWire != WireId()) { + auto scores_it = wireScores.find(conflictPipWire); + if (scores_it != wireScores.end()) + penalty_delta += scores_it->second * cfg.wireRipupPenalty; + penalty_delta += cfg.wireRipupPenalty; + } + + if (conflictWireNet != nullptr) { + auto scores_it = netScores.find(conflictWireNet); + if (scores_it != netScores.end()) + penalty_delta += scores_it->second * cfg.netRipupPenalty; + penalty_delta += cfg.netRipupPenalty; + penalty_delta += conflictWireNet->wires.size() * cfg.wireRipupPenalty; + } + + if (conflictPipNet != nullptr) { + auto scores_it = netScores.find(conflictPipNet); + if (scores_it != netScores.end()) + penalty_delta += scores_it->second * cfg.netRipupPenalty; + penalty_delta += cfg.netRipupPenalty; + penalty_delta += conflictPipNet->wires.size() * cfg.wireRipupPenalty; + } + } + + next_penalty += penalty_delta * (timing_driven ? std::max(0.05, (1.0 - crit)) : 1); + + delay_t next_score = next_delay + next_penalty; + NPNR_ASSERT(next_score >= 0); + + if ((best_score >= 0) && (next_score - next_bonus - cfg.estimatePrecision > best_score)) + continue; + + auto old_visited_it = visited.find(next_wire); + if (old_visited_it != visited.end()) { + delay_t old_delay = old_visited_it->second.delay; + delay_t old_score = old_delay + old_visited_it->second.penalty; + NPNR_ASSERT(old_score >= 0); + + if (next_score + ctx->getDelayEpsilon() >= old_score) + continue; + +#if 0 + if (ctx->debug) + log("Found better route to %s. Old vs new delay estimate: %.3f (%.3f) %.3f (%.3f)\n", + ctx->nameOfWire(next_wire), + ctx->getDelayNS(old_score), + ctx->getDelayNS(old_visited_it->second.delay), + ctx->getDelayNS(next_score), + ctx->getDelayNS(next_delay)); +#endif + } + + QueuedWire next_qw; + next_qw.wire = next_wire; + next_qw.pip = pip; + next_qw.delay = next_delay; + next_qw.penalty = next_penalty; + next_qw.bonus = next_bonus; + if (cfg.useEstimate) { + next_qw.togo = ctx->estimateDelay(next_wire, dst_wire); + delay_t this_est = next_qw.delay + next_qw.togo; + if (this_est / 2 - cfg.estimatePrecision > best_est) + continue; + if (best_est > this_est) + best_est = this_est; + } + next_qw.randtag = ctx->rng(); + +#if 0 + if (ctx->debug) + log("%s -> %s: %.3f (%.3f)\n", + ctx->nameOfWire(qw.wire), + ctx->nameOfWire(next_wire), + ctx->getDelayNS(next_score), + ctx->getDelayNS(next_delay)); +#endif + + visited[next_qw.wire] = next_qw; + queue.push(next_qw); + + if (next_wire == dst_wire) { + maxVisitCnt = std::min(maxVisitCnt, 2 * visitCnt + (next_qw.penalty > 0 ? 100 : 0)); + best_score = next_score - next_bonus; + } + } + } + + if (ctx->debug) + log(" total number of visited nodes: %d\n", visitCnt); + + if (visited.count(dst_wire) == 0) { + if (ctx->debug) + log(" no route found for this arc\n"); + return false; + } + + if (ctx->debug) { + log(" final route delay: %8.2f\n", ctx->getDelayNS(visited[dst_wire].delay)); + log(" final route penalty: %8.2f\n", ctx->getDelayNS(visited[dst_wire].penalty)); + log(" final route bonus: %8.2f\n", ctx->getDelayNS(visited[dst_wire].bonus)); + log(" arc budget: %12.2f\n", ctx->getDelayNS(net_info->users[user_idx].budget)); + } + + // bind resulting route (and maybe unroute other nets) + + pool unassign_wires = arc_to_wires[arc]; + + WireId cursor = dst_wire; + delay_t accumulated_path_delay = 0; + delay_t last_path_delay_delta = 0; + while (1) { + auto pip = visited[cursor].pip; + + if (ctx->debug) { + delay_t path_delay_delta = ctx->estimateDelay(cursor, dst_wire) - accumulated_path_delay; + + log(" node %s (%+.2f %+.2f)\n", ctx->nameOfWire(cursor), ctx->getDelayNS(path_delay_delta), + ctx->getDelayNS(path_delay_delta - last_path_delay_delta)); + + last_path_delay_delta = path_delay_delta; + + if (pip != PipId()) + accumulated_path_delay += ctx->getPipDelay(pip).maxDelay(); + accumulated_path_delay += ctx->getWireDelay(cursor).maxDelay(); + } + + if (pip == PipId()) + NPNR_ASSERT(cursor == src_wire); + + if (!net_info->wires.count(cursor) || net_info->wires.at(cursor).pip != pip) { + if (!ctx->checkWireAvail(cursor)) { + ripup_wire(cursor); + NPNR_ASSERT(ctx->checkWireAvail(cursor)); + } + + if (pip != PipId() && !ctx->checkPipAvail(pip)) { + ripup_pip(pip); + NPNR_ASSERT(ctx->checkPipAvail(pip)); + } + + if (pip == PipId()) { + if (ctx->debug) + log(" bind wire %s\n", ctx->nameOfWire(cursor)); + ctx->bindWire(cursor, net_info, STRENGTH_WEAK); + } else { + if (ctx->debug) + log(" bind pip %s\n", ctx->nameOfPip(pip)); + ctx->bindPip(pip, net_info, STRENGTH_WEAK); + } + } + + wire_to_arcs[cursor].insert(arc); + arc_to_wires[arc].insert(cursor); + + if (pip == PipId()) + break; + + cursor = ctx->getPipSrcWire(pip); + } + + if (ripup_flag) + arcs_with_ripup++; + else + arcs_without_ripup++; + + return true; + } + + delay_t find_slack_thresh() + { + // If more than 5% of arcs have negative slack; use the 5% threshold as a ripup criteria + int arc_count = 0; + int failed_count = 0; + delay_t default_thresh = ctx->getDelayEpsilon(); + + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (skip_net(ni)) + continue; + for (auto &usr : ni->users) { + ++arc_count; + delay_t slack = tmg.get_setup_slack(CellPortKey(usr)); + if (slack == std::numeric_limits::min()) + continue; + if (slack < default_thresh) + ++failed_count; + } + } + + if (arc_count < 50 || (failed_count < (0.05 * arc_count))) { + return default_thresh; + } + + std::vector slacks; + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (skip_net(ni)) + continue; + for (auto &usr : ni->users) { + delay_t slack = tmg.get_setup_slack(CellPortKey(usr)); + if (slack == std::numeric_limits::min()) + continue; + slacks.push_back(slack); + } + } + std::sort(slacks.begin(), slacks.end()); + delay_t thresh = slacks.at(int(slacks.size() * 0.05)); + log_warning("%.f%% of arcs have failing slack; using %.2fns as ripup threshold. Consider a reduced Fmax " + "constraint.\n", + (100.0 * failed_count) / arc_count, ctx->getDelayNS(thresh)); + return thresh; + } +}; + +} // namespace + +NEXTPNR_NAMESPACE_BEGIN + +Router1Cfg::Router1Cfg(Context *ctx) +{ + maxIterCnt = ctx->setting("router1/maxIterCnt", 200); + cleanupReroute = ctx->setting("router1/cleanupReroute", true); + fullCleanupReroute = ctx->setting("router1/fullCleanupReroute", true); + useEstimate = ctx->setting("router1/useEstimate", true); + + wireRipupPenalty = ctx->getRipupDelayPenalty(); + netRipupPenalty = 10 * ctx->getRipupDelayPenalty(); + reuseBonus = wireRipupPenalty / 2; + + estimatePrecision = 100 * ctx->getRipupDelayPenalty(); +} + +bool router1(Context *ctx, const Router1Cfg &cfg) +{ + try { + log_break(); + log_info("Routing..\n"); + ScopeLock lock(ctx); + auto rstart = std::chrono::high_resolution_clock::now(); + + log_info("Setting up routing queue.\n"); + + Router1 router(ctx, cfg); + router.setup(); +#ifndef NDEBUG + router.check(); +#endif + + log_info("Routing %d arcs.\n", int(router.arc_queue.size())); + + int iter_cnt = 0; + int last_arcs_with_ripup = 0; + int last_arcs_without_ripup = 0; + int timing_fail_count = 0; + bool timing_ripup = ctx->setting("router/tmg_ripup", false); + delay_t ripup_slack = 0; + + log_info(" | (re-)routed arcs | delta | remaining| time spent |\n"); + log_info(" IterCnt | w/ripup wo/ripup | w/r wo/r | arcs| batch(sec) total(sec)|\n"); + + auto prev_time = rstart; + while (!router.arc_queue.empty()) { + if (++iter_cnt % 1000 == 0) { + auto curr_time = std::chrono::high_resolution_clock::now(); + log_info("%10d | %8d %10d | %4d %5d | %9d| %10.02f %10.02f|\n", iter_cnt, router.arcs_with_ripup, + router.arcs_without_ripup, router.arcs_with_ripup - last_arcs_with_ripup, + router.arcs_without_ripup - last_arcs_without_ripup, int(router.arc_queue.size()), + std::chrono::duration(curr_time - prev_time).count(), + std::chrono::duration(curr_time - rstart).count()); + prev_time = curr_time; + last_arcs_with_ripup = router.arcs_with_ripup; + last_arcs_without_ripup = router.arcs_without_ripup; + ctx->yield(); +#ifndef NDEBUG + router.check(); +#endif + } + + if (ctx->debug) + log("-- %d --\n", iter_cnt); + + arc_key arc = router.arc_queue_pop(); + + if (!router.route_arc(arc, true)) { + log_warning("Failed to find a route for arc %d of net %s.\n", arc.user_idx.idx(), + ctx->nameOf(arc.net_info)); +#ifndef NDEBUG + router.check(); + ctx->check(); +#endif + return false; + } + // Timing driven ripup + if (timing_ripup && router.arc_queue.empty() && timing_fail_count < 50) { + ++timing_fail_count; + router.tmg.run(); + delay_t wns = 0, tns = 0; + if (timing_fail_count == 1) + ripup_slack = router.find_slack_thresh(); + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (router.skip_net(ni)) + continue; + bool is_locked = false; + for (auto &wire : ni->wires) { + if (wire.second.strength > STRENGTH_STRONG) + is_locked = true; + } + if (is_locked) + continue; + for (auto &usr : ni->users) { + delay_t slack = router.tmg.get_setup_slack(CellPortKey(usr)); + if (slack == std::numeric_limits::min()) + continue; + if (slack < 0) { + wns = std::min(wns, slack); + tns += slack; + } + if (slack <= ripup_slack) { + for (WireId w : ctx->getNetinfoSinkWires(ni, usr)) { + if (ctx->checkWireAvail(w)) + continue; + router.ripup_wire(w); + } + } + } + } + log_info(" %d arcs ripped up due to negative slack WNS=%.02fns TNS=%.02fns.\n", + int(router.arc_queue.size()), ctx->getDelayNS(wns), ctx->getDelayNS(tns)); + iter_cnt = 0; + router.wireScores.clear(); + router.netScores.clear(); + } + } + auto rend = std::chrono::high_resolution_clock::now(); + log_info("%10d | %8d %10d | %4d %5d | %9d| %10.02f %10.02f|\n", iter_cnt, router.arcs_with_ripup, + router.arcs_without_ripup, router.arcs_with_ripup - last_arcs_with_ripup, + router.arcs_without_ripup - last_arcs_without_ripup, int(router.arc_queue.size()), + std::chrono::duration(rend - prev_time).count(), + std::chrono::duration(rend - rstart).count()); + log_info("Routing complete.\n"); + ctx->yield(); + log_info("Router1 time %.02fs\n", std::chrono::duration(rend - rstart).count()); + +#ifndef NDEBUG + router.check(); + ctx->check(); + log_assert(ctx->checkRoutedDesign()); +#endif + + log_info("Checksum: 0x%08x\n", ctx->checksum()); + timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */, + true /* warn_on_failure */, true /* update_results */); + + return true; + } catch (log_execution_error_exception) { +#ifndef NDEBUG + ctx->lock(); + ctx->check(); + ctx->unlock(); +#endif + return false; + } +} + +bool Context::checkRoutedDesign() const +{ + const Context *ctx = getCtx(); + + for (auto &net_it : ctx->nets) { + NetInfo *net_info = net_it.second.get(); + +#ifdef ARCH_ECP5 + if (net_info->is_global) + continue; +#endif + + if (ctx->debug) + log("checking net %s\n", ctx->nameOf(net_info)); + + if (net_info->users.empty()) { + if (ctx->debug) + log(" net without sinks\n"); + log_assert(net_info->wires.empty()); + continue; + } + + bool found_unrouted = false; + bool found_loop = false; + bool found_stub = false; + + struct ExtraWireInfo + { + int order_num = 0; + pool children; + }; + + dict> db; + + for (auto &it : net_info->wires) { + WireId w = it.first; + PipId p = it.second.pip; + + if (p != PipId()) { + log_assert(ctx->getPipDstWire(p) == w); + db.emplace(ctx->getPipSrcWire(p), std::make_unique()).first->second->children.insert(w); + } + } + + auto src_wire = ctx->getNetinfoSourceWire(net_info); + if (src_wire == WireId()) { + log_assert(net_info->driver.cell == nullptr); + if (ctx->debug) + log(" undriven and unrouted\n"); + continue; + } + + if (net_info->wires.count(src_wire) == 0) { + if (ctx->debug) + log(" source (%s) not bound to net\n", ctx->nameOfWire(src_wire)); + found_unrouted = true; + } + + dict> dest_wires; + for (auto user : net_info->users.enumerate()) { + for (auto dst_wire : ctx->getNetinfoSinkWires(net_info, user.value)) { + log_assert(dst_wire != WireId()); + dest_wires[dst_wire] = user.index; + + if (net_info->wires.count(dst_wire) == 0) { + if (ctx->debug) + log(" sink %d (%s) not bound to net\n", user.index.idx(), ctx->nameOfWire(dst_wire)); + found_unrouted = true; + } + } + } + + std::function setOrderNum; + pool logged_wires; + + setOrderNum = [&](WireId w, int num) { + auto &db_entry = *db.emplace(w, std::make_unique()).first->second; + if (db_entry.order_num != 0) { + found_loop = true; + log(" %*s=> loop\n", 2 * num, ""); + return; + } + db_entry.order_num = num; + for (WireId child : db_entry.children) { + if (ctx->debug) { + log(" %*s-> %s\n", 2 * num, "", ctx->nameOfWire(child)); + logged_wires.insert(child); + } + setOrderNum(child, num + 1); + } + if (db_entry.children.empty()) { + if (dest_wires.count(w) != 0) { + if (ctx->debug) + log(" %*s=> sink %d\n", 2 * num, "", dest_wires.at(w).idx()); + } else { + if (ctx->debug) + log(" %*s=> stub\n", 2 * num, ""); + found_stub = true; + } + } + }; + + if (ctx->debug) { + log(" driver: %s\n", ctx->nameOfWire(src_wire)); + logged_wires.insert(src_wire); + } + setOrderNum(src_wire, 1); + + pool dangling_wires; + + for (auto &it : db) { + auto &db_entry = *it.second; + if (db_entry.order_num == 0) + dangling_wires.insert(it.first); + } + + if (ctx->debug) { + if (dangling_wires.empty()) { + log(" no dangling wires.\n"); + } else { + pool root_wires = dangling_wires; + + for (WireId w : dangling_wires) { + for (WireId c : db[w]->children) + root_wires.erase(c); + } + + for (WireId w : root_wires) { + log(" dangling wire: %s\n", ctx->nameOfWire(w)); + logged_wires.insert(w); + setOrderNum(w, 1); + } + + for (WireId w : dangling_wires) { + if (logged_wires.count(w) == 0) + log(" loop: %s -> %s\n", ctx->nameOfWire(ctx->getPipSrcWire(net_info->wires.at(w).pip)), + ctx->nameOfWire(w)); + } + } + } + + bool fail = false; + + if (found_unrouted) { + if (ctx->debug) + log("check failed: found unrouted arcs\n"); + fail = true; + } + + if (found_loop) { + if (ctx->debug) + log("check failed: found loops\n"); + fail = true; + } + + if (found_stub) { + if (ctx->debug) + log("check failed: found stubs\n"); + fail = true; + } + + if (!dangling_wires.empty()) { + if (ctx->debug) + log("check failed: found dangling wires\n"); + fail = true; + } + + if (fail) + return false; + } + + return true; +} + +bool Context::getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t *delay, dict *route, + bool useEstimate) +{ + // FIXME + return false; +} + +NEXTPNR_NAMESPACE_END diff --git a/common/route/router1.h b/common/route/router1.h new file mode 100644 index 00000000..a7ec5bad --- /dev/null +++ b/common/route/router1.h @@ -0,0 +1,45 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Xenia Wolf + * + * 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 ROUTER1_H +#define ROUTER1_H + +#include "log.h" +#include "nextpnr.h" +NEXTPNR_NAMESPACE_BEGIN + +struct Router1Cfg +{ + Router1Cfg(Context *ctx); + + int maxIterCnt; + bool cleanupReroute; + bool fullCleanupReroute; + bool useEstimate; + delay_t wireRipupPenalty; + delay_t netRipupPenalty; + delay_t reuseBonus; + delay_t estimatePrecision; +}; + +extern bool router1(Context *ctx, const Router1Cfg &cfg); + +NEXTPNR_NAMESPACE_END + +#endif // ROUTER1_H diff --git a/common/route/router2.cc b/common/route/router2.cc new file mode 100644 index 00000000..e943e493 --- /dev/null +++ b/common/route/router2.cc @@ -0,0 +1,1499 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 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. + * + * Core routing algorithm based on CRoute: + * + * CRoute: A Fast High-quality Timing-driven Connection-based FPGA Router + * Dries Vercruyce, Elias Vansteenkiste and Dirk Stroobandt + * DOI 10.1109/FCCM.2019.00017 [PDF on SciHub] + * + * Modified for the nextpnr Arch API and data structures; optimised for + * real-world FPGA architectures in particular ECP5 and Xilinx UltraScale+ + * + */ + +#include "router2.h" + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "nextpnr.h" +#include "router1.h" +#include "scope_lock.h" +#include "timing.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +struct Router2 +{ + + struct PerArcData + { + WireId sink_wire; + ArcBounds bb; + bool routed = false; + }; + + // As we allow overlap at first; the nextpnr bind functions can't be used + // as the primary relation between arcs and wires/pips + struct PerNetData + { + WireId src_wire; + dict> wires; + std::vector> arcs; + ArcBounds bb; + // Coordinates of the center of the net, used for the weight-to-average + int cx, cy, hpwl; + int total_route_us = 0; + float max_crit = 0; + int fail_count = 0; + }; + + struct WireScore + { + float cost; + float togo_cost; + float total() const { return cost + togo_cost; } + }; + + struct PerWireData + { + // nextpnr + WireId w; + // Historical congestion cost + int curr_cong = 0; + float hist_cong_cost = 1.0; + // Wire is unavailable as locked to another arc + bool unavailable = false; + // This wire has to be used for this net + int reserved_net = -1; + // The notional location of the wire, to guarantee thread safety + int16_t x = 0, y = 0; + // Visit data + PipId pip_fwd, pip_bwd; + bool visited_fwd = false, visited_bwd = false; + }; + + Context *ctx; + Router2Cfg cfg; + + Router2(Context *ctx, const Router2Cfg &cfg) : ctx(ctx), cfg(cfg), tmg(ctx) { tmg.setup(); } + + // Use 'udata' for fast net lookups and indexing + std::vector nets_by_udata; + std::vector nets; + + bool timing_driven, timing_driven_ripup; + TimingAnalyser tmg; + + void setup_nets() + { + // Populate per-net and per-arc structures at start of routing + nets.resize(ctx->nets.size()); + nets_by_udata.resize(ctx->nets.size()); + size_t i = 0; + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + ni->udata = i; + nets_by_udata.at(i) = ni; + nets.at(i).arcs.resize(ni->users.capacity()); + + // Start net bounding box at overall min/max + nets.at(i).bb.x0 = std::numeric_limits::max(); + nets.at(i).bb.x1 = std::numeric_limits::min(); + nets.at(i).bb.y0 = std::numeric_limits::max(); + nets.at(i).bb.y1 = std::numeric_limits::min(); + nets.at(i).cx = 0; + nets.at(i).cy = 0; + + if (ni->driver.cell != nullptr) { + Loc drv_loc = ctx->getBelLocation(ni->driver.cell->bel); + nets.at(i).cx += drv_loc.x; + nets.at(i).cy += drv_loc.y; + } + + for (auto usr : ni->users.enumerate()) { + WireId src_wire = ctx->getNetinfoSourceWire(ni); + for (auto &dst_wire : ctx->getNetinfoSinkWires(ni, usr.value)) { + nets.at(i).src_wire = src_wire; + if (ni->driver.cell == nullptr) + src_wire = dst_wire; + if (ni->driver.cell == nullptr && dst_wire == WireId()) + continue; + if (src_wire == WireId()) + log_error("No wire found for port %s on source cell %s.\n", ctx->nameOf(ni->driver.port), + ctx->nameOf(ni->driver.cell)); + if (dst_wire == WireId()) + log_error("No wire found for port %s on destination cell %s.\n", ctx->nameOf(usr.value.port), + ctx->nameOf(usr.value.cell)); + nets.at(i).arcs.at(usr.index.idx()).emplace_back(); + auto &ad = nets.at(i).arcs.at(usr.index.idx()).back(); + ad.sink_wire = dst_wire; + // Set bounding box for this arc + ad.bb = ctx->getRouteBoundingBox(src_wire, dst_wire); + // Expand net bounding box to include this arc + nets.at(i).bb.x0 = std::min(nets.at(i).bb.x0, ad.bb.x0); + nets.at(i).bb.x1 = std::max(nets.at(i).bb.x1, ad.bb.x1); + nets.at(i).bb.y0 = std::min(nets.at(i).bb.y0, ad.bb.y0); + nets.at(i).bb.y1 = std::max(nets.at(i).bb.y1, ad.bb.y1); + } + // Add location to centroid sum + Loc usr_loc = ctx->getBelLocation(usr.value.cell->bel); + nets.at(i).cx += usr_loc.x; + nets.at(i).cy += usr_loc.y; + } + nets.at(i).hpwl = std::max( + std::abs(nets.at(i).bb.y1 - nets.at(i).bb.y0) + std::abs(nets.at(i).bb.x1 - nets.at(i).bb.x0), 1); + nets.at(i).cx /= int(ni->users.entries() + 1); + nets.at(i).cy /= int(ni->users.entries() + 1); + if (ctx->debug) + log_info("%s: bb=(%d, %d)->(%d, %d) c=(%d, %d) hpwl=%d\n", ctx->nameOf(ni), nets.at(i).bb.x0, + nets.at(i).bb.y0, nets.at(i).bb.x1, nets.at(i).bb.y1, nets.at(i).cx, nets.at(i).cy, + nets.at(i).hpwl); + nets.at(i).bb.x0 = std::max(nets.at(i).bb.x0 - cfg.bb_margin_x, 0); + nets.at(i).bb.y0 = std::max(nets.at(i).bb.y0 - cfg.bb_margin_y, 0); + nets.at(i).bb.x1 = std::min(nets.at(i).bb.x1 + cfg.bb_margin_x, ctx->getGridDimX()); + nets.at(i).bb.y1 = std::min(nets.at(i).bb.y1 + cfg.bb_margin_y, ctx->getGridDimY()); + i++; + } + } + + dict wire_to_idx; + std::vector flat_wires; + + PerWireData &wire_data(WireId w) { return flat_wires[wire_to_idx.at(w)]; } + + void setup_wires() + { + // Set up per-wire structures, so that MT parts don't have to do any memory allocation + // This is possibly quite wasteful and not cache-optimal; further consideration necessary + for (auto wire : ctx->getWires()) { + PerWireData pwd; + pwd.w = wire; + NetInfo *bound = ctx->getBoundWireNet(wire); + if (bound != nullptr) { + auto iter = bound->wires.find(wire); + if (iter != bound->wires.end()) { + auto &nd = nets.at(bound->udata); + nd.wires[wire] = std::make_pair(bound->wires.at(wire).pip, 0); + pwd.curr_cong = 1; + if (bound->wires.at(wire).strength == STRENGTH_PLACER) { + pwd.reserved_net = bound->udata; + } else if (bound->wires.at(wire).strength > STRENGTH_PLACER) { + pwd.unavailable = true; + } + } + } + + ArcBounds wire_loc = ctx->getRouteBoundingBox(wire, wire); + pwd.x = (wire_loc.x0 + wire_loc.x1) / 2; + pwd.y = (wire_loc.y0 + wire_loc.y1) / 2; + + wire_to_idx[wire] = int(flat_wires.size()); + flat_wires.push_back(pwd); + } + + for (auto &net_pair : ctx->nets) { + auto *net = net_pair.second.get(); + auto &nd = nets.at(net->udata); + for (auto usr : net->users.enumerate()) { + auto &ad = nd.arcs.at(usr.index.idx()); + for (size_t phys_pin = 0; phys_pin < ad.size(); phys_pin++) { + if (check_arc_routing(net, usr.index, phys_pin)) { + record_prerouted_net(net, usr.index, phys_pin); + } + } + } + } + } + + struct QueuedWire + { + + explicit QueuedWire(int wire = -1, WireScore score = WireScore{}, int randtag = 0) + : wire(wire), score(score), randtag(randtag){}; + + int wire; + WireScore score; + int randtag = 0; + + struct Greater + { + bool operator()(const QueuedWire &lhs, const QueuedWire &rhs) const noexcept + { + float lhs_score = lhs.score.cost + lhs.score.togo_cost, + rhs_score = rhs.score.cost + rhs.score.togo_cost; + return lhs_score == rhs_score ? lhs.randtag > rhs.randtag : lhs_score > rhs_score; + } + }; + }; + + bool hit_test_pip(ArcBounds &bb, Loc l) { return l.x >= bb.x0 && l.x <= bb.x1 && l.y >= bb.y0 && l.y <= bb.y1; } + + double curr_cong_weight, hist_cong_weight, estimate_weight; + + struct ThreadContext + { + // Nets to route + std::vector route_nets; + // Nets that failed routing + std::vector failed_nets; + + std::vector, size_t>> route_arcs; + + std::priority_queue, QueuedWire::Greater> fwd_queue, bwd_queue; + // Special case where one net has multiple logical arcs to the same physical sink + pool processed_sinks; + + std::vector dirty_wires; + + // Thread bounding box + ArcBounds bb; + + DeterministicRNG rng; + + // Used to add existing routing to the heap + pool in_wire_by_loc; + dict, pool> wire_by_loc; + }; + + bool thread_test_wire(ThreadContext &t, PerWireData &w) + { + return w.x >= t.bb.x0 && w.x <= t.bb.x1 && w.y >= t.bb.y0 && w.y <= t.bb.y1; + } + + enum ArcRouteResult + { + ARC_SUCCESS, + ARC_RETRY_WITHOUT_BB, + ARC_FATAL, + }; + +// Define to make sure we don't print in a multithreaded context +#define ARC_LOG_ERR(...) \ + do { \ + if (is_mt) \ + return ARC_FATAL; \ + else \ + log_error(__VA_ARGS__); \ + } while (0) +#define ROUTE_LOG_DBG(...) \ + do { \ + if (!is_mt && ctx->debug) \ + log(__VA_ARGS__); \ + } while (0) + + void bind_pip_internal(PerNetData &net, store_index user, int wire, PipId pip) + { + auto &wd = flat_wires.at(wire); + auto found = net.wires.find(wd.w); + if (found == net.wires.end()) { + // Not yet used for any arcs of this net, add to list + net.wires.emplace(wd.w, std::make_pair(pip, 1)); + // Increase bound count of wire by 1 + ++wd.curr_cong; + } else { + // Already used for at least one other arc of this net + // Don't allow two uphill PIPs for the same net and wire + NPNR_ASSERT(found->second.first == pip); + // Increase the count of bound arcs + ++found->second.second; + } + } + + void unbind_pip_internal(PerNetData &net, store_index user, WireId wire) + { + auto &wd = wire_data(wire); + auto &b = net.wires.at(wd.w); + --b.second; + if (b.second == 0) { + // No remaining arcs of this net bound to this wire + --wd.curr_cong; + net.wires.erase(wd.w); + } + } + + void ripup_arc(NetInfo *net, store_index user, size_t phys_pin) + { + auto &nd = nets.at(net->udata); + auto &ad = nd.arcs.at(user.idx()).at(phys_pin); + if (!ad.routed) + return; + WireId src = nets.at(net->udata).src_wire; + WireId cursor = ad.sink_wire; + while (cursor != src) { + PipId pip = nd.wires.at(cursor).first; + unbind_pip_internal(nd, user, cursor); + cursor = ctx->getPipSrcWire(pip); + } + ad.routed = false; + } + + float score_wire_for_arc(NetInfo *net, store_index user, size_t phys_pin, WireId wire, PipId pip, + float crit_weight) + { + auto &wd = wire_data(wire); + auto &nd = nets.at(net->udata); + float base_cost = cfg.get_base_cost(ctx, wire, pip, crit_weight); + int overuse = wd.curr_cong; + float hist_cost = 1.0f + crit_weight * (wd.hist_cong_cost - 1.0f); + float bias_cost = 0; + int source_uses = 0; + if (nd.wires.count(wire)) { + overuse -= 1; + source_uses = nd.wires.at(wire).second; + } + float present_cost = 1.0f + overuse * curr_cong_weight * crit_weight; + if (pip != PipId()) { + Loc pl = ctx->getPipLocation(pip); + bias_cost = cfg.bias_cost_factor * (base_cost / int(net->users.entries())) * + ((std::abs(pl.x - nd.cx) + std::abs(pl.y - nd.cy)) / float(nd.hpwl)); + } + return base_cost * hist_cost * present_cost / (1 + (source_uses * crit_weight)) + bias_cost; + } + + float get_togo_cost(NetInfo *net, store_index user, int wire, WireId src_sink, float crit_weight, + bool bwd = false) + { + auto &nd = nets.at(net->udata); + auto &wd = flat_wires[wire]; + int source_uses = 0; + if (nd.wires.count(wd.w)) { + source_uses = nd.wires.at(wd.w).second; + } + // FIXME: timing/wirelength balance? + delay_t est_delay = ctx->estimateDelay(bwd ? src_sink : wd.w, bwd ? wd.w : src_sink); + return (ctx->getDelayNS(est_delay) / (1 + source_uses * crit_weight)) + cfg.ipin_cost_adder; + } + + bool check_arc_routing(NetInfo *net, store_index usr, size_t phys_pin) + { + auto &nd = nets.at(net->udata); + auto &ad = nd.arcs.at(usr.idx()).at(phys_pin); + WireId src_wire = nets.at(net->udata).src_wire; + WireId cursor = ad.sink_wire; + while (nd.wires.count(cursor)) { + auto &wd = wire_data(cursor); + if (wd.curr_cong != 1) + return false; + auto &uh = nd.wires.at(cursor).first; + if (uh == PipId()) + break; + cursor = ctx->getPipSrcWire(uh); + } + return (cursor == src_wire); + } + + void record_prerouted_net(NetInfo *net, store_index usr, size_t phys_pin) + { + auto &nd = nets.at(net->udata); + auto &ad = nd.arcs.at(usr.idx()).at(phys_pin); + ad.routed = true; + + WireId src = nets.at(net->udata).src_wire; + WireId cursor = ad.sink_wire; + while (cursor != src) { + size_t wire_idx = wire_to_idx.at(cursor); + PipId pip = nd.wires.at(cursor).first; + bind_pip_internal(nd, usr, wire_idx, pip); + cursor = ctx->getPipSrcWire(pip); + } + } + + // Returns true if a wire contains no source ports or driving pips + bool is_wire_undriveable(WireId wire, const NetInfo *net, int iter_count = 0) + { + // This is specifically designed to handle a particularly icky case that the current router struggles with in + // the nexus device, + // C -> C lut input only + // C; D; or F from another lut -> D lut input + // D or M -> M ff input + // without careful reservation of C for C lut input and D for D lut input, there is fighting for D between FF + // and LUT + if (iter_count > 7) + return false; // heuristic to assume we've hit general routing + if (wire_data(wire).unavailable) + return true; + if (wire_data(wire).reserved_net != -1 && wire_data(wire).reserved_net != net->udata) + return true; // reserved for another net + for (auto bp : ctx->getWireBelPins(wire)) + if ((net->driver.cell == nullptr || bp.bel == net->driver.cell->bel) && + ctx->getBelPinType(bp.bel, bp.pin) != PORT_IN) + return false; + for (auto p : ctx->getPipsUphill(wire)) + if (ctx->checkPipAvail(p)) { + if (!is_wire_undriveable(ctx->getPipSrcWire(p), net, iter_count + 1)) + return false; + } + return true; + } + + // Find all the wires that must be used to route a given arc + bool reserve_wires_for_arc(NetInfo *net, store_index i) + { + bool did_something = false; + WireId src = ctx->getNetinfoSourceWire(net); + auto &usr = net->users.at(i); + for (auto sink : ctx->getNetinfoSinkWires(net, usr)) { + pool rsv; + WireId cursor = sink; + bool done = false; + if (ctx->debug) + log("reserving wires for arc %d (%s.%s) of net %s\n", i.idx(), ctx->nameOf(usr.cell), + ctx->nameOf(usr.port), ctx->nameOf(net)); + while (!done) { + auto &wd = wire_data(cursor); + if (ctx->debug) + log(" %s\n", ctx->nameOfWire(cursor)); + did_something |= (wd.reserved_net != net->udata); + if (wd.reserved_net != -1 && wd.reserved_net != net->udata) + log_error("attempting to reserve wire '%s' for nets '%s' and '%s'\n", ctx->nameOfWire(cursor), + ctx->nameOf(nets_by_udata.at(wd.reserved_net)), ctx->nameOf(net)); + wd.reserved_net = net->udata; + if (cursor == src) + break; + WireId next_cursor; + for (auto uh : ctx->getPipsUphill(cursor)) { + WireId w = ctx->getPipSrcWire(uh); + if (is_wire_undriveable(w, net)) + continue; + if (next_cursor != WireId()) { + done = true; + break; + } + next_cursor = w; + } + if (next_cursor == WireId()) + break; + cursor = next_cursor; + } + } + return did_something; + } + + void find_all_reserved_wires() + { + // Run iteratively, as reserving wires for one net might limit choices for another + bool did_something = false; + do { + did_something = false; + for (auto net : nets_by_udata) { + WireId src = ctx->getNetinfoSourceWire(net); + if (src == WireId()) + continue; + for (auto usr : net->users.enumerate()) + did_something |= reserve_wires_for_arc(net, usr.index); + } + } while (did_something); + } + + void reset_wires(ThreadContext &t) + { + for (auto w : t.dirty_wires) { + flat_wires[w].pip_fwd = PipId(); + flat_wires[w].pip_bwd = PipId(); + flat_wires[w].visited_fwd = false; + flat_wires[w].visited_bwd = false; + } + t.dirty_wires.clear(); + } + + // These nets have very-high-fanout pips and special rules must be followed (only working backwards) to avoid + // crippling perf + bool is_pseudo_const_net(const NetInfo *net) + { +#ifdef ARCH_NEXUS + if (net->driver.cell != nullptr && net->driver.cell->type == id_VCC_DRV) + return true; +#endif + return false; + } + + void update_wire_by_loc(ThreadContext &t, NetInfo *net, store_index i, size_t phys_pin, bool is_mt) + { + if (is_pseudo_const_net(net)) + return; + auto &nd = nets.at(net->udata); + auto &ad = nd.arcs.at(i.idx()).at(phys_pin); + WireId cursor = ad.sink_wire; + if (!nd.wires.count(cursor)) + return; + while (cursor != nd.src_wire) { + if (!t.in_wire_by_loc.count(cursor)) { + t.in_wire_by_loc.insert(cursor); + for (auto dh : ctx->getPipsDownhill(cursor)) { + Loc dh_loc = ctx->getPipLocation(dh); + t.wire_by_loc[std::make_pair(dh_loc.x, dh_loc.y)].insert(cursor); + } + } + cursor = ctx->getPipSrcWire(nd.wires.at(cursor).first); + } + } + + // Functions for marking wires as visited, and checking if they have already been visited + void set_visited_fwd(ThreadContext &t, int wire, PipId pip) + { + auto &wd = flat_wires.at(wire); + if (!wd.visited_fwd && !wd.visited_bwd) + t.dirty_wires.push_back(wire); + wd.pip_fwd = pip; + wd.visited_fwd = true; + } + void set_visited_bwd(ThreadContext &t, int wire, PipId pip) + { + auto &wd = flat_wires.at(wire); + if (!wd.visited_fwd && !wd.visited_bwd) + t.dirty_wires.push_back(wire); + wd.pip_bwd = pip; + wd.visited_bwd = true; + } + + bool was_visited_fwd(int wire) { return flat_wires.at(wire).visited_fwd; } + bool was_visited_bwd(int wire) { return flat_wires.at(wire).visited_bwd; } + + float get_arc_crit(NetInfo *net, store_index i) + { + if (!timing_driven) + return 0; + return tmg.get_criticality(CellPortKey(net->users.at(i))); + } + + bool arc_failed_slack(NetInfo *net, store_index usr_idx) + { + return timing_driven_ripup && + (tmg.get_setup_slack(CellPortKey(net->users.at(usr_idx))) < (2 * ctx->getDelayEpsilon())); + } + + ArcRouteResult route_arc(ThreadContext &t, NetInfo *net, store_index i, size_t phys_pin, bool is_mt, + bool is_bb = true) + { + // Do some initial lookups and checks + auto arc_start = std::chrono::high_resolution_clock::now(); + auto &nd = nets[net->udata]; + auto &ad = nd.arcs.at(i.idx()).at(phys_pin); + auto &usr = net->users.at(i); + ROUTE_LOG_DBG("Routing arc %d of net '%s' (%d, %d) -> (%d, %d)\n", i.idx(), ctx->nameOf(net), ad.bb.x0, + ad.bb.y0, ad.bb.x1, ad.bb.y1); + WireId src_wire = ctx->getNetinfoSourceWire(net), dst_wire = ctx->getNetinfoSinkWire(net, usr, phys_pin); + if (src_wire == WireId()) + ARC_LOG_ERR("No wire found for port %s on source cell %s.\n", ctx->nameOf(net->driver.port), + ctx->nameOf(net->driver.cell)); + if (dst_wire == WireId()) + ARC_LOG_ERR("No wire found for port %s on destination cell %s.\n", ctx->nameOf(usr.port), + ctx->nameOf(usr.cell)); + int src_wire_idx = wire_to_idx.at(src_wire); + int dst_wire_idx = wire_to_idx.at(dst_wire); + // Calculate a timing weight based on criticality + float crit = get_arc_crit(net, i); + float crit_weight = (1.0f - std::pow(crit, 2)); + ROUTE_LOG_DBG(" crit=%.3f crit_weight=%.3f\n", crit, crit_weight); + // Check if arc was already done _in this iteration_ + if (t.processed_sinks.count(dst_wire)) + return ARC_SUCCESS; + + // We have two modes: + // 0. starting within a small range of existing routing + // 1. expanding from all routing + int mode = 0; + if (net->users.entries() < 4 || nd.wires.empty() || (crit > 0.95)) + mode = 1; + + // This records the point where forwards and backwards routing met + int midpoint_wire = -1; + int explored = 1; + + for (; mode < 2; mode++) { + // Clear out the queues + if (!t.fwd_queue.empty()) { + std::priority_queue, QueuedWire::Greater> new_queue; + t.fwd_queue.swap(new_queue); + } + if (!t.bwd_queue.empty()) { + std::priority_queue, QueuedWire::Greater> new_queue; + t.bwd_queue.swap(new_queue); + } + // Unvisit any previously visited wires + reset_wires(t); + + ROUTE_LOG_DBG("src_wire = %s -> dst_wire = %s\n", ctx->nameOfWire(src_wire), ctx->nameOfWire(dst_wire)); + + // Add 'forward' direction startpoints to queue + auto seed_queue_fwd = [&](WireId wire, float wire_cost = 0) { + WireScore base_score; + base_score.cost = wire_cost; + int wire_idx = wire_to_idx.at(wire); + base_score.togo_cost = get_togo_cost(net, i, wire_idx, dst_wire, false, crit_weight); + t.fwd_queue.push(QueuedWire(wire_idx, base_score)); + set_visited_fwd(t, wire_idx, PipId()); + }; + auto &dst_data = flat_wires.at(dst_wire_idx); + // Look for nearby existing routing + for (int dy = -cfg.bb_margin_y; dy <= cfg.bb_margin_y; dy++) + for (int dx = -cfg.bb_margin_x; dx <= cfg.bb_margin_x; dx++) { + auto fnd = t.wire_by_loc.find(std::make_pair(dst_data.x + dx, dst_data.y + dy)); + if (fnd == t.wire_by_loc.end()) + continue; + for (WireId wire : fnd->second) { + ROUTE_LOG_DBG(" seeding with %s\n", ctx->nameOfWire(wire)); + seed_queue_fwd(wire); + } + } + + if (mode == 0 && t.fwd_queue.size() < 4) + continue; + + if (mode == 1 && !is_pseudo_const_net(net)) { + // Seed forwards with the source wire, if less than 8 existing wires added + seed_queue_fwd(src_wire); + } else { + set_visited_fwd(t, src_wire_idx, PipId()); + } + auto seed_queue_bwd = [&](WireId wire) { + WireScore base_score; + base_score.cost = 0; + int wire_idx = wire_to_idx.at(wire); + base_score.togo_cost = get_togo_cost(net, i, wire_idx, src_wire, true, crit_weight); + t.bwd_queue.push(QueuedWire(wire_idx, base_score)); + set_visited_bwd(t, wire_idx, PipId()); + }; + + // Seed backwards with the dest wire + seed_queue_bwd(dst_wire); + + int toexplore = 25000 * std::max(1, (ad.bb.x1 - ad.bb.x0) + (ad.bb.y1 - ad.bb.y0)); + int iter = 0; + + // Mode 0 required both queues to be live + while (((mode == 0) ? (!t.fwd_queue.empty() && !t.bwd_queue.empty()) + : (!t.fwd_queue.empty() || !t.bwd_queue.empty())) && + (!is_bb || iter < toexplore)) { + ++iter; + if (!t.fwd_queue.empty()) { + // Explore forwards + auto curr = t.fwd_queue.top(); + t.fwd_queue.pop(); + ++explored; + if (was_visited_bwd(curr.wire)) { + // Meet in the middle; done + midpoint_wire = curr.wire; + break; + } + auto &curr_data = flat_wires.at(curr.wire); + for (PipId dh : ctx->getPipsDownhill(curr_data.w)) { + // Skip pips outside of box in bounding-box mode + if (is_bb && !hit_test_pip(nd.bb, ctx->getPipLocation(dh))) + continue; + if (!ctx->checkPipAvailForNet(dh, net)) + continue; + WireId next = ctx->getPipDstWire(dh); + int next_idx = wire_to_idx.at(next); + if (was_visited_fwd(next_idx)) { + // Don't expand the same node twice. + continue; + } + auto &nwd = flat_wires.at(next_idx); + if (nwd.unavailable) + continue; + // Reserved for another net + if (nwd.reserved_net != -1 && nwd.reserved_net != net->udata) + continue; + // Don't allow the same wire to be bound to the same net with a different driving pip + auto fnd_wire = nd.wires.find(next); + if (fnd_wire != nd.wires.end() && fnd_wire->second.first != dh) + continue; + if (!thread_test_wire(t, nwd)) + continue; // thread safety issue + WireScore next_score; + next_score.cost = curr.score.cost + score_wire_for_arc(net, i, phys_pin, next, dh, crit_weight); + next_score.togo_cost = + cfg.estimate_weight * get_togo_cost(net, i, next_idx, dst_wire, false, crit_weight); + set_visited_fwd(t, next_idx, dh); + t.fwd_queue.push(QueuedWire(next_idx, next_score, t.rng.rng())); + } + } + if (!t.bwd_queue.empty()) { + // Explore backwards + auto curr = t.bwd_queue.top(); + t.bwd_queue.pop(); + ++explored; + if (was_visited_fwd(curr.wire)) { + // Meet in the middle; done + midpoint_wire = curr.wire; + break; + } + auto &curr_data = flat_wires.at(curr.wire); + // Don't allow the same wire to be bound to the same net with a different driving pip + PipId bound_pip; + auto fnd_wire = nd.wires.find(curr_data.w); + if (fnd_wire != nd.wires.end()) + bound_pip = fnd_wire->second.first; + + for (PipId uh : ctx->getPipsUphill(curr_data.w)) { + if (bound_pip != PipId() && bound_pip != uh) + continue; + if (is_bb && !hit_test_pip(nd.bb, ctx->getPipLocation(uh))) + continue; + if (!ctx->checkPipAvailForNet(uh, net)) + continue; + WireId next = ctx->getPipSrcWire(uh); + int next_idx = wire_to_idx.at(next); + if (was_visited_bwd(next_idx)) { + // Don't expand the same node twice. + continue; + } + auto &nwd = flat_wires.at(next_idx); + if (nwd.unavailable) + continue; + // Reserved for another net + if (nwd.reserved_net != -1 && nwd.reserved_net != net->udata) + continue; + if (!thread_test_wire(t, nwd)) + continue; // thread safety issue + WireScore next_score; + next_score.cost = curr.score.cost + score_wire_for_arc(net, i, phys_pin, next, uh, crit_weight); + next_score.togo_cost = + cfg.estimate_weight * get_togo_cost(net, i, next_idx, src_wire, true, crit_weight); + set_visited_bwd(t, next_idx, uh); + t.bwd_queue.push(QueuedWire(next_idx, next_score, t.rng.rng())); + } + } + } + if (midpoint_wire != -1) + break; + } + ArcRouteResult result = ARC_SUCCESS; + if (midpoint_wire != -1) { + ROUTE_LOG_DBG(" Routed (explored %d wires): ", explored); + int cursor_bwd = midpoint_wire; + while (was_visited_fwd(cursor_bwd)) { + PipId pip = flat_wires.at(cursor_bwd).pip_fwd; + if (pip == PipId() && cursor_bwd != src_wire_idx) + break; + bind_pip_internal(nd, i, cursor_bwd, pip); + if (ctx->debug && !is_mt) { + auto &wd = flat_wires.at(cursor_bwd); + ROUTE_LOG_DBG(" fwd wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w), + wd.curr_cong - 1, wd.hist_cong_cost, nd.wires.at(wd.w).second); + } + if (pip == PipId()) { + break; + } + ROUTE_LOG_DBG(" fwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x, + ctx->getPipLocation(pip).y); + cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip)); + } + + while (cursor_bwd != src_wire_idx) { + // Tack onto existing routing + WireId bwd_w = flat_wires.at(cursor_bwd).w; + if (!nd.wires.count(bwd_w)) + break; + auto &bound = nd.wires.at(bwd_w); + PipId pip = bound.first; + if (ctx->debug && !is_mt) { + auto &wd = flat_wires.at(cursor_bwd); + ROUTE_LOG_DBG(" ext wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w), + wd.curr_cong - 1, wd.hist_cong_cost, bound.second); + } + bind_pip_internal(nd, i, cursor_bwd, pip); + if (pip == PipId()) + break; + cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip)); + } + + NPNR_ASSERT(cursor_bwd == src_wire_idx); + + int cursor_fwd = midpoint_wire; + while (was_visited_bwd(cursor_fwd)) { + PipId pip = flat_wires.at(cursor_fwd).pip_bwd; + if (pip == PipId()) { + break; + } + ROUTE_LOG_DBG(" bwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x, + ctx->getPipLocation(pip).y); + cursor_fwd = wire_to_idx.at(ctx->getPipDstWire(pip)); + bind_pip_internal(nd, i, cursor_fwd, pip); + if (ctx->debug && !is_mt) { + auto &wd = flat_wires.at(cursor_fwd); + ROUTE_LOG_DBG(" bwd wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w), + wd.curr_cong - 1, wd.hist_cong_cost, nd.wires.at(wd.w).second); + } + } + NPNR_ASSERT(cursor_fwd == dst_wire_idx); + + update_wire_by_loc(t, net, i, phys_pin, is_mt); + t.processed_sinks.insert(dst_wire); + ad.routed = true; + auto arc_end = std::chrono::high_resolution_clock::now(); + ROUTE_LOG_DBG("Routing arc %d of net '%s' (is_bb = %d) took %02fs\n", i.idx(), ctx->nameOf(net), is_bb, + std::chrono::duration(arc_end - arc_start).count()); + } else { + auto arc_end = std::chrono::high_resolution_clock::now(); + ROUTE_LOG_DBG("Failed routing arc %d of net '%s' (is_bb = %d) took %02fs\n", i.idx(), ctx->nameOf(net), + is_bb, std::chrono::duration(arc_end - arc_start).count()); + result = ARC_RETRY_WITHOUT_BB; + } + reset_wires(t); + return result; + } +#undef ARC_ERR + + bool route_net(ThreadContext &t, NetInfo *net, bool is_mt) + { + +#ifdef ARCH_ECP5 + if (net->is_global) + return true; +#endif + + ROUTE_LOG_DBG("Routing net '%s'...\n", ctx->nameOf(net)); + + auto rstart = std::chrono::high_resolution_clock::now(); + + // Nothing to do if net is undriven + if (net->driver.cell == nullptr) + return true; + + bool have_failures = false; + t.processed_sinks.clear(); + t.route_arcs.clear(); + t.wire_by_loc.clear(); + t.in_wire_by_loc.clear(); + auto &nd = nets.at(net->udata); + bool failed_slack = false; + for (auto usr : net->users.enumerate()) + failed_slack |= arc_failed_slack(net, usr.index); + for (auto usr : net->users.enumerate()) { + auto &ad = nd.arcs.at(usr.index.idx()); + for (size_t j = 0; j < ad.size(); j++) { + // Ripup failed arcs to start with + // Check if arc is already legally routed + if (!failed_slack && check_arc_routing(net, usr.index, j)) { + update_wire_by_loc(t, net, usr.index, j, true); + continue; + } + + // Ripup arc to start with + ripup_arc(net, usr.index, j); + t.route_arcs.emplace_back(usr.index, j); + } + } + // Route most critical arc first + std::stable_sort(t.route_arcs.begin(), t.route_arcs.end(), + [&](std::pair, size_t> a, std::pair, size_t> b) { + return get_arc_crit(net, a.first) > get_arc_crit(net, b.first); + }); + for (auto a : t.route_arcs) { + auto res1 = route_arc(t, net, a.first, a.second, is_mt, true); + if (res1 == ARC_FATAL) + return false; // Arc failed irrecoverably + else if (res1 == ARC_RETRY_WITHOUT_BB) { + if (is_mt) { + // Can't break out of bounding box in multi-threaded mode, so mark this arc as a failure + have_failures = true; + } else { + // Attempt a re-route without the bounding box constraint + ROUTE_LOG_DBG("Rerouting arc %d.%d of net '%s' without bounding box, possible tricky routing...\n", + a.first.idx(), int(a.second), ctx->nameOf(net)); + auto res2 = route_arc(t, net, a.first, a.second, is_mt, false); + // If this also fails, no choice but to give up + if (res2 != ARC_SUCCESS) { + if (ctx->debug) { + log_info("Pre-bound routing: \n"); + for (auto &wire_pair : net->wires) { + log(" %s", ctx->nameOfWire(wire_pair.first)); + if (wire_pair.second.pip != PipId()) + log(" %s", ctx->nameOfPip(wire_pair.second.pip)); + log("\n"); + } + } + log_error("Failed to route arc %d.%d of net '%s', from %s to %s.\n", a.first.idx(), + int(a.second), ctx->nameOf(net), ctx->nameOfWire(ctx->getNetinfoSourceWire(net)), + ctx->nameOfWire(ctx->getNetinfoSinkWire(net, net->users.at(a.first), a.second))); + } + } + } + } + if (cfg.perf_profile) { + auto rend = std::chrono::high_resolution_clock::now(); + nets.at(net->udata).total_route_us += + (std::chrono::duration_cast(rend - rstart).count()); + } + return !have_failures; + } +#undef ROUTE_LOG_DBG + + int total_wire_use = 0; + int overused_wires = 0; + int total_overuse = 0; + std::vector route_queue; + std::set failed_nets; + + void update_congestion() + { + total_overuse = 0; + overused_wires = 0; + total_wire_use = 0; + failed_nets.clear(); + pool already_updated; + for (size_t i = 0; i < nets.size(); i++) { + auto &nd = nets.at(i); + for (const auto &w : nd.wires) { + ++total_wire_use; + auto &wd = wire_data(w.first); + if (wd.curr_cong > 1) { + if (already_updated.count(w.first)) { + ++total_overuse; + } else { + if (curr_cong_weight > 0) + wd.hist_cong_cost = + std::min(1e9, wd.hist_cong_cost + (wd.curr_cong - 1) * hist_cong_weight); + already_updated.insert(w.first); + ++overused_wires; + } + failed_nets.insert(i); + } + } + } + for (int n : failed_nets) { + auto &net_data = nets.at(n); + ++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 + } + } + } + + bool bind_and_check(NetInfo *net, store_index usr_idx, int phys_pin) + { +#ifdef ARCH_ECP5 + if (net->is_global) + return true; +#endif + bool success = true; + auto &nd = nets.at(net->udata); + auto &ad = nd.arcs.at(usr_idx.idx()).at(phys_pin); + auto &usr = net->users.at(usr_idx); + WireId src = ctx->getNetinfoSourceWire(net); + // Skip routes with no source + if (src == WireId()) + return true; + WireId dst = ctx->getNetinfoSinkWire(net, usr, phys_pin); + if (dst == WireId()) + return true; + + // Skip routes where there is no routing (special cases) + if (!ad.routed) { + if ((src == dst) && ctx->getBoundWireNet(dst) != net) + ctx->bindWire(src, net, STRENGTH_WEAK); + if (ctx->debug) { + log("Net %s not routed, not binding\n", ctx->nameOf(net)); + } + return true; + } + + WireId cursor = dst; + + std::vector to_bind; + + while (cursor != src) { + if (!ctx->checkWireAvail(cursor)) { + NetInfo *bound_net = ctx->getBoundWireNet(cursor); + if (bound_net != net) { + if (ctx->verbose) { + if (bound_net != nullptr) { + log_info("Failed to bind wire %s to net %s, bound to net %s\n", ctx->nameOfWire(cursor), + net->name.c_str(ctx), bound_net->name.c_str(ctx)); + } else { + log_info("Failed to bind wire %s to net %s, bound net nullptr\n", ctx->nameOfWire(cursor), + net->name.c_str(ctx)); + } + } + success = false; + break; + } + } + if (!nd.wires.count(cursor)) { + log("Failure details:\n"); + log(" Cursor: %s\n", ctx->nameOfWire(cursor)); + log_error("Internal error; incomplete route tree for arc %d of net %s.\n", usr_idx.idx(), + ctx->nameOf(net)); + } + PipId p = nd.wires.at(cursor).first; + if (ctx->checkPipAvailForNet(p, net)) { + NetInfo *bound_net = ctx->getBoundPipNet(p); + if (bound_net == nullptr) { + to_bind.push_back(p); + } + } else { + if (ctx->verbose) { + log_info("Failed to bind pip %s to net %s\n", ctx->nameOfPip(p), net->name.c_str(ctx)); + } + success = false; + break; + } + cursor = ctx->getPipSrcWire(p); + } + + if (success) { + if (ctx->getBoundWireNet(src) == nullptr) + ctx->bindWire(src, net, STRENGTH_WEAK); + for (auto tb : to_bind) + ctx->bindPip(tb, net, STRENGTH_WEAK); + } else { + ripup_arc(net, usr_idx, phys_pin); + failed_nets.insert(net->udata); + } + return success; + } + + int arch_fail = 0; + bool bind_and_check_all() + { + // Make sure arch is internally consistent before we mess with it. + ctx->check(); + + bool success = true; + std::vector net_wires; + for (auto net : nets_by_udata) { +#ifdef ARCH_ECP5 + if (net->is_global) + continue; +#endif + // Ripup wires and pips used by the net in nextpnr's structures + net_wires.clear(); + for (auto &w : net->wires) { + if (w.second.strength <= STRENGTH_STRONG) { + net_wires.push_back(w.first); + } else if (ctx->debug) { + log("Net %s didn't rip up wire %s because strength was %d\n", ctx->nameOf(net), + ctx->nameOfWire(w.first), w.second.strength); + } + } + for (auto w : net_wires) + ctx->unbindWire(w); + + if (ctx->debug) { + log("Ripped up %zu wires on net %s\n", net_wires.size(), ctx->nameOf(net)); + } + + // Bind the arcs using the routes we have discovered + for (auto usr : net->users.enumerate()) { + for (size_t phys_pin = 0; phys_pin < nets.at(net->udata).arcs.at(usr.index.idx()).size(); phys_pin++) { + if (!bind_and_check(net, usr.index, phys_pin)) { + ++arch_fail; + success = false; + } + } + } + } + + // Check that the arch is still internally consistent! + ctx->check(); + + return success; + } + + void write_wiretype_heatmap(std::ostream &out) + { + dict> cong_by_type; + size_t max_cong = 0; + // Build histogram + for (auto &wd : flat_wires) { + size_t val = wd.curr_cong; + IdString type = ctx->getWireType(wd.w); + max_cong = std::max(max_cong, val); + if (cong_by_type[type].size() <= max_cong) + cong_by_type[type].resize(max_cong + 1); + cong_by_type[type].at(val) += 1; + } + // Write csv + out << "type,"; + for (size_t i = 0; i <= max_cong; i++) + out << "bound=" << i << ","; + out << std::endl; + for (auto &ty : cong_by_type) { + out << ctx->nameOf(ty.first) << ","; + for (int count : ty.second) + out << count << ","; + out << std::endl; + } + } + + int mid_x = 0, mid_y = 0; + + void partition_nets() + { + // Create a histogram of positions in X and Y positions + std::map cxs, cys; + for (auto &n : nets) { + if (n.cx != -1) + ++cxs[n.cx]; + if (n.cy != -1) + ++cys[n.cy]; + } + // 4-way split for now + int accum_x = 0, accum_y = 0; + int halfway = int(nets.size()) / 2; + for (auto &p : cxs) { + if (accum_x < halfway && (accum_x + p.second) >= halfway) + mid_x = p.first; + accum_x += p.second; + } + for (auto &p : cys) { + if (accum_y < halfway && (accum_y + p.second) >= halfway) + mid_y = p.first; + accum_y += p.second; + } + if (ctx->verbose) { + log_info(" x splitpoint: %d\n", mid_x); + log_info(" y splitpoint: %d\n", mid_y); + } + std::vector bins(5, 0); + for (auto &n : nets) { + if (n.bb.x0 < mid_x && n.bb.x1 < mid_x && n.bb.y0 < mid_y && n.bb.y1 < mid_y) + ++bins[0]; // TL + else if (n.bb.x0 >= mid_x && n.bb.x1 >= mid_x && n.bb.y0 < mid_y && n.bb.y1 < mid_y) + ++bins[1]; // TR + else if (n.bb.x0 < mid_x && n.bb.x1 < mid_x && n.bb.y0 >= mid_y && n.bb.y1 >= mid_y) + ++bins[2]; // BL + else if (n.bb.x0 >= mid_x && n.bb.x1 >= mid_x && n.bb.y0 >= mid_y && n.bb.y1 >= mid_y) + ++bins[3]; // BR + else + ++bins[4]; // cross-boundary + } + if (ctx->verbose) + for (int i = 0; i < 5; i++) + log_info(" bin %d N=%d\n", i, bins[i]); + } + + void router_thread(ThreadContext &t, bool is_mt) + { + for (auto n : t.route_nets) { + bool result = route_net(t, n, is_mt); + if (!result) + t.failed_nets.push_back(n); + } + } + + void do_route() + { + // Don't multithread if fewer than 200 nets (heuristic) + if (route_queue.size() < 200) { + ThreadContext st; + st.rng.rngseed(ctx->rng64()); + st.bb = ArcBounds(0, 0, std::numeric_limits::max(), std::numeric_limits::max()); + for (size_t j = 0; j < route_queue.size(); j++) { + route_net(st, nets_by_udata[route_queue[j]], false); + } + return; + } + const int Nq = 4, Nv = 2, Nh = 2; + const int N = Nq + Nv + Nh; + std::vector tcs(N + 1); + for (auto &th : tcs) { + th.rng.rngseed(ctx->rng64()); + } + int le_x = mid_x; + int rs_x = mid_x; + int le_y = mid_y; + int rs_y = mid_y; + // Set up thread bounding boxes + tcs.at(0).bb = ArcBounds(0, 0, mid_x, mid_y); + tcs.at(1).bb = ArcBounds(mid_x + 1, 0, std::numeric_limits::max(), le_y); + tcs.at(2).bb = ArcBounds(0, mid_y + 1, mid_x, std::numeric_limits::max()); + tcs.at(3).bb = + ArcBounds(mid_x + 1, mid_y + 1, std::numeric_limits::max(), std::numeric_limits::max()); + + tcs.at(4).bb = ArcBounds(0, 0, std::numeric_limits::max(), mid_y); + tcs.at(5).bb = ArcBounds(0, mid_y + 1, std::numeric_limits::max(), std::numeric_limits::max()); + + tcs.at(6).bb = ArcBounds(0, 0, mid_x, std::numeric_limits::max()); + tcs.at(7).bb = ArcBounds(mid_x + 1, 0, std::numeric_limits::max(), std::numeric_limits::max()); + + tcs.at(8).bb = ArcBounds(0, 0, std::numeric_limits::max(), std::numeric_limits::max()); + + for (auto n : route_queue) { + auto &nd = nets.at(n); + auto ni = nets_by_udata.at(n); + int bin = N; + // Quadrants + if (nd.bb.x0 < le_x && nd.bb.x1 < le_x && nd.bb.y0 < le_y && nd.bb.y1 < le_y) + bin = 0; + else if (nd.bb.x0 >= rs_x && nd.bb.x1 >= rs_x && nd.bb.y0 < le_y && nd.bb.y1 < le_y) + bin = 1; + else if (nd.bb.x0 < le_x && nd.bb.x1 < le_x && nd.bb.y0 >= rs_y && nd.bb.y1 >= rs_y) + bin = 2; + else if (nd.bb.x0 >= rs_x && nd.bb.x1 >= rs_x && nd.bb.y0 >= rs_y && nd.bb.y1 >= rs_y) + bin = 3; + // Vertical split + else if (nd.bb.y0 < le_y && nd.bb.y1 < le_y) + bin = Nq + 0; + else if (nd.bb.y0 >= rs_y && nd.bb.y1 >= rs_y) + bin = Nq + 1; + // Horizontal split + else if (nd.bb.x0 < le_x && nd.bb.x1 < le_x) + bin = Nq + Nv + 0; + else if (nd.bb.x0 >= rs_x && nd.bb.x1 >= rs_x) + bin = Nq + Nv + 1; + tcs.at(bin).route_nets.push_back(ni); + } + if (ctx->verbose) + log_info("%d/%d nets not multi-threadable\n", int(tcs.at(N).route_nets.size()), int(route_queue.size())); +#ifdef NPNR_DISABLE_THREADS + // Singlethreaded routing - quadrants + for (int i = 0; i < Nq; i++) { + router_thread(tcs.at(i), /*is_mt=*/false); + } + // Vertical splits + for (int i = Nq; i < Nq + Nv; i++) { + router_thread(tcs.at(i), /*is_mt=*/false); + } + // Horizontal splits + for (int i = Nq + Nv; i < Nq + Nv + Nh; i++) { + router_thread(tcs.at(i), /*is_mt=*/false); + } +#else + // Multithreaded part of routing - quadrants + std::vector threads; + for (int i = 0; i < Nq; i++) { + threads.emplace_back([this, &tcs, i]() { router_thread(tcs.at(i), /*is_mt=*/true); }); + } + for (auto &t : threads) + t.join(); + threads.clear(); + // Vertical splits + for (int i = Nq; i < Nq + Nv; i++) { + threads.emplace_back([this, &tcs, i]() { router_thread(tcs.at(i), /*is_mt=*/true); }); + } + for (auto &t : threads) + t.join(); + threads.clear(); + // Horizontal splits + for (int i = Nq + Nv; i < Nq + Nv + Nh; i++) { + threads.emplace_back([this, &tcs, i]() { router_thread(tcs.at(i), /*is_mt=*/true); }); + } + for (auto &t : threads) + t.join(); + threads.clear(); +#endif + // Singlethreaded part of routing - nets that cross partitions + // or don't fit within bounding box + for (auto st_net : tcs.at(N).route_nets) + route_net(tcs.at(N), st_net, false); + // Failed nets + for (int i = 0; i < N; i++) + for (auto fail : tcs.at(i).failed_nets) + route_net(tcs.at(N), fail, false); + } + + delay_t get_route_delay(int net, store_index usr_idx, int phys_idx) + { + auto &nd = nets.at(net); + auto &ad = nd.arcs.at(usr_idx.idx()).at(phys_idx); + WireId cursor = ad.sink_wire; + if (cursor == WireId() || nd.src_wire == WireId()) + return 0; + delay_t delay = 0; + while (true) { + delay += ctx->getWireDelay(cursor).maxDelay(); + if (!nd.wires.count(cursor)) + break; + auto &bound = nd.wires.at(cursor); + if (bound.first == PipId()) + break; + delay += ctx->getPipDelay(bound.first).maxDelay(); + cursor = ctx->getPipSrcWire(bound.first); + } + NPNR_ASSERT(cursor == nd.src_wire); + return delay; + } + + void update_route_delays() + { + for (int net : route_queue) { + NetInfo *ni = nets_by_udata.at(net); +#ifdef ARCH_ECP5 + if (ni->is_global) + continue; +#endif + auto &nd = nets.at(net); + for (auto usr : ni->users.enumerate()) { + delay_t arc_delay = 0; + for (int j = 0; j < int(nd.arcs.at(usr.index.idx()).size()); j++) + arc_delay = std::max(arc_delay, get_route_delay(net, usr.index, j)); + tmg.set_route_delay(CellPortKey(usr.value), DelayPair(arc_delay)); + } + } + } + + void operator()() + { + log_info("Running router2...\n"); + log_info("Setting up routing resources...\n"); + auto rstart = std::chrono::high_resolution_clock::now(); + setup_nets(); + setup_wires(); + find_all_reserved_wires(); + partition_nets(); + curr_cong_weight = cfg.init_curr_cong_weight; + hist_cong_weight = cfg.hist_cong_weight; + ThreadContext st; + int iter = 1; + + ScopeLock lock(ctx); + + for (size_t i = 0; i < nets_by_udata.size(); i++) + route_queue.push_back(i); + + timing_driven = ctx->setting("timing_driven"); + if (ctx->settings.count(ctx->id("router/tmg_ripup"))) + timing_driven_ripup = timing_driven && ctx->setting("router/tmg_ripup"); + else + timing_driven_ripup = false; + log_info("Running main router loop...\n"); + if (timing_driven) + tmg.run(true); + do { + ctx->sorted_shuffle(route_queue); + + if (timing_driven && int(route_queue.size()) >= 30) { + for (auto n : route_queue) { + NetInfo *ni = nets_by_udata.at(n); + auto &net = nets.at(n); + net.max_crit = 0; + for (auto &usr : ni->users) { + float c = tmg.get_criticality(CellPortKey(usr)); + net.max_crit = std::max(net.max_crit, c); + } + } + std::stable_sort(route_queue.begin(), route_queue.end(), + [&](int na, int nb) { return nets.at(na).max_crit > nets.at(nb).max_crit; }); + } + + do_route(); + update_route_delays(); + route_queue.clear(); + update_congestion(); + + if (!cfg.heatmap.empty()) { + std::string filename(cfg.heatmap + "_" + std::to_string(iter) + ".csv"); + std::ofstream cong_map(filename); + if (!cong_map) + log_error("Failed to open wiretype heatmap %s for writing.\n", filename.c_str()); + write_wiretype_heatmap(cong_map); + log_info(" wrote wiretype heatmap to %s.\n", filename.c_str()); + } + int tmgfail = 0; + if (timing_driven) + tmg.run(false); + if (timing_driven_ripup && iter < 500) { + for (size_t i = 0; i < nets_by_udata.size(); i++) { + NetInfo *ni = nets_by_udata.at(i); + for (auto usr : ni->users.enumerate()) { + if (arc_failed_slack(ni, usr.index)) { + failed_nets.insert(i); + ++tmgfail; + } + } + } + } + if (overused_wires == 0 && tmgfail == 0) { + // Try and actually bind nextpnr Arch API wires + bind_and_check_all(); + } + for (auto cn : failed_nets) + route_queue.push_back(cn); + if (timing_driven_ripup) + log_info(" iter=%d wires=%d overused=%d overuse=%d tmgfail=%d archfail=%s\n", iter, total_wire_use, + overused_wires, total_overuse, tmgfail, + (overused_wires > 0 || tmgfail > 0) ? "NA" : std::to_string(arch_fail).c_str()); + else + log_info(" iter=%d wires=%d overused=%d overuse=%d archfail=%s\n", iter, total_wire_use, + overused_wires, total_overuse, + (overused_wires > 0 || tmgfail > 0) ? "NA" : std::to_string(arch_fail).c_str()); + ++iter; + if (curr_cong_weight < 1e9) + curr_cong_weight += cfg.curr_cong_mult; + } while (!failed_nets.empty()); + if (cfg.perf_profile) { + std::vector> nets_by_runtime; + for (auto &n : nets_by_udata) { + nets_by_runtime.emplace_back(nets.at(n->udata).total_route_us, n->name); + } + std::sort(nets_by_runtime.begin(), nets_by_runtime.end(), std::greater>()); + log_info("1000 slowest nets by runtime:\n"); + for (int i = 0; i < std::min(int(nets_by_runtime.size()), 1000); i++) { + log(" %80s %6d %.1fms\n", nets_by_runtime.at(i).second.c_str(ctx), + int(ctx->nets.at(nets_by_runtime.at(i).second)->users.entries()), + nets_by_runtime.at(i).first / 1000.0); + } + } + auto rend = std::chrono::high_resolution_clock::now(); + log_info("Router2 time %.02fs\n", std::chrono::duration(rend - rstart).count()); + + log_info("Running router1 to check that route is legal...\n"); + + lock.unlock_early(); + + router1(ctx, Router1Cfg(ctx)); + } +}; +} // namespace + +void router2(Context *ctx, const Router2Cfg &cfg) +{ + Router2 rt(ctx, cfg); + rt.ctx = ctx; + rt(); +} + +Router2Cfg::Router2Cfg(Context *ctx) +{ + backwards_max_iter = ctx->setting("router2/bwdMaxIter", 20); + global_backwards_max_iter = ctx->setting("router2/glbBwdMaxIter", 200); + bb_margin_x = ctx->setting("router2/bbMargin/x", 3); + bb_margin_y = ctx->setting("router2/bbMargin/y", 3); + ipin_cost_adder = ctx->setting("router2/ipinCostAdder", 0.0f); + bias_cost_factor = ctx->setting("router2/biasCostFactor", 0.25f); + init_curr_cong_weight = ctx->setting("router2/initCurrCongWeight", 0.5f); + hist_cong_weight = ctx->setting("router2/histCongWeight", 1.0f); + curr_cong_mult = ctx->setting("router2/currCongWeightMult", 2.0f); + estimate_weight = ctx->setting("router2/estimateWeight", 1.25f); + perf_profile = ctx->setting("router2/perfProfile", false); + if (ctx->settings.count(ctx->id("router2/heatmap"))) + heatmap = ctx->settings.at(ctx->id("router2/heatmap")).as_string(); + else + heatmap = ""; +} + +NEXTPNR_NAMESPACE_END diff --git a/common/route/router2.h b/common/route/router2.h new file mode 100644 index 00000000..629453c6 --- /dev/null +++ b/common/route/router2.h @@ -0,0 +1,66 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2019 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 + +inline float default_base_cost(Context *ctx, WireId wire, PipId pip, float crit_weight) +{ + (void)crit_weight; // unused + return ctx->getDelayNS(ctx->getPipDelay(pip).maxDelay() + ctx->getWireDelay(wire).maxDelay() + + ctx->getDelayEpsilon()); +} + +struct Router2Cfg +{ + Router2Cfg(Context *ctx); + + // Maximum iterations for backwards routing attempt + int backwards_max_iter; + // Maximum iterations for backwards routing attempt for global nets + int global_backwards_max_iter; + // Padding added to bounding boxes to account for imperfect routing, + // congestion, etc + int bb_margin_x, bb_margin_y; + // Cost factor added to input pin wires; effectively reduces the + // benefit of sharing interconnect + float ipin_cost_adder; + // Cost factor for "bias" towards center location of net + float bias_cost_factor; + // Starting current and historical congestion cost factor + float init_curr_cong_weight, hist_cong_weight; + // Current congestion cost multiplier + float curr_cong_mult; + + // Weight given to delay estimate in A*. Higher values + // mean faster and more directed routing, at the risk + // of choosing a less congestion/delay-optimal route + float estimate_weight; + + // Print additional performance profiling information + bool perf_profile = false; + + std::string heatmap; + std::function get_base_cost = default_base_cost; +}; + +void router2(Context *ctx, const Router2Cfg &cfg); + +NEXTPNR_NAMESPACE_END diff --git a/common/router1.cc b/common/router1.cc deleted file mode 100644 index 98132116..00000000 --- a/common/router1.cc +++ /dev/null @@ -1,1175 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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 -#include - -#include "log.h" -#include "router1.h" -#include "scope_lock.h" -#include "timing.h" - -namespace { - -USING_NEXTPNR_NAMESPACE - -struct arc_key -{ - NetInfo *net_info; - // logical user cell port index - store_index user_idx; - // physical index into cell->bel pin mapping (usually 0) - unsigned phys_idx; - - bool operator==(const arc_key &other) const - { - return (net_info == other.net_info) && (user_idx == other.user_idx) && (phys_idx == other.phys_idx); - } - bool operator<(const arc_key &other) const - { - return net_info == other.net_info - ? (user_idx == other.user_idx ? phys_idx < other.phys_idx : user_idx < other.user_idx) - : net_info->name < other.net_info->name; - } - - unsigned int hash() const - { - std::size_t seed = std::hash()(net_info); - seed ^= user_idx.hash() + 0x9e3779b9 + (seed << 6) + (seed >> 2); - seed ^= std::hash()(phys_idx) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - return seed; - } -}; - -struct arc_entry -{ - arc_key arc; - delay_t pri; - int randtag = 0; - - struct Less - { - bool operator()(const arc_entry &lhs, const arc_entry &rhs) const noexcept - { - if (lhs.pri != rhs.pri) - return lhs.pri < rhs.pri; - return lhs.randtag < rhs.randtag; - } - }; -}; - -struct QueuedWire -{ - WireId wire; - PipId pip; - - delay_t delay = 0, penalty = 0, bonus = 0, togo = 0; - int randtag = 0; - - struct Greater - { - bool operator()(const QueuedWire &lhs, const QueuedWire &rhs) const noexcept - { - delay_t l = lhs.delay + lhs.penalty + lhs.togo; - delay_t r = rhs.delay + rhs.penalty + rhs.togo; - NPNR_ASSERT(l >= 0); - NPNR_ASSERT(r >= 0); - l -= lhs.bonus; - r -= rhs.bonus; - return l == r ? lhs.randtag > rhs.randtag : l > r; - } - }; -}; - -struct Router1 -{ - Context *ctx; - const Router1Cfg &cfg; - - std::priority_queue, arc_entry::Less> arc_queue; - dict> wire_to_arcs; - dict> arc_to_wires; - pool queued_arcs; - - std::priority_queue, QueuedWire::Greater> queue; - - dict wireScores; - dict netScores; - - int arcs_with_ripup = 0; - int arcs_without_ripup = 0; - bool ripup_flag; - - TimingAnalyser tmg; - - bool timing_driven = true; - - Router1(Context *ctx, const Router1Cfg &cfg) : ctx(ctx), cfg(cfg), tmg(ctx) - { - timing_driven = ctx->setting("timing_driven"); - tmg.setup(); - tmg.run(); - } - - void arc_queue_insert(const arc_key &arc, WireId src_wire, WireId dst_wire) - { - if (queued_arcs.count(arc)) - return; - - delay_t pri = ctx->estimateDelay(src_wire, dst_wire) * - (100 * tmg.get_criticality(CellPortKey(arc.net_info->users.at(arc.user_idx)))); - - arc_entry entry; - entry.arc = arc; - entry.pri = pri; - entry.randtag = ctx->rng(); - -#if 0 - if (ctx->debug) - log("[arc_queue_insert] %s (%d) %s %s [%d %d]\n", ctx->nameOf(entry.arc.net_info), entry.arc.user_idx, - ctx->nameOfWire(src_wire), ctx->nameOfWire(dst_wire), (int)entry.pri, entry.randtag); -#endif - - arc_queue.push(entry); - queued_arcs.insert(arc); - } - - void arc_queue_insert(const arc_key &arc) - { - if (queued_arcs.count(arc)) - return; - - NetInfo *net_info = arc.net_info; - auto user_idx = arc.user_idx; - unsigned phys_idx = arc.phys_idx; - - auto src_wire = ctx->getNetinfoSourceWire(net_info); - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx], phys_idx); - - arc_queue_insert(arc, src_wire, dst_wire); - } - - arc_key arc_queue_pop() - { - arc_entry entry = arc_queue.top(); - -#if 0 - if (ctx->debug) - log("[arc_queue_pop] %s (%d) [%d %d]\n", ctx->nameOf(entry.arc.net_info), entry.arc.user_idx, - (int)entry.pri, entry.randtag); -#endif - - arc_queue.pop(); - queued_arcs.erase(entry.arc); - return entry.arc; - } - - void ripup_net(NetInfo *net) - { - if (ctx->debug) - log(" ripup net %s\n", ctx->nameOf(net)); - - netScores[net]++; - - std::vector wires; - for (auto &it : net->wires) - wires.push_back(it.first); - - ctx->sorted_shuffle(wires); - - for (WireId w : wires) { - std::vector arcs; - for (auto &it : wire_to_arcs[w]) { - arc_to_wires[it].erase(w); - arcs.push_back(it); - } - wire_to_arcs[w].clear(); - - ctx->sorted_shuffle(arcs); - - for (auto &it : arcs) - arc_queue_insert(it); - - if (ctx->debug) - log(" unbind wire %s\n", ctx->nameOfWire(w)); - - ctx->unbindWire(w); - wireScores[w]++; - } - - ripup_flag = true; - } - - void ripup_wire(WireId wire, int extra_indent = 0) - { - if (ctx->debug) - log(" ripup wire %s\n", ctx->nameOfWire(wire)); - - WireId w = ctx->getConflictingWireWire(wire); - - if (w == WireId()) { - NetInfo *n = ctx->getConflictingWireNet(wire); - if (n != nullptr) - ripup_net(n); - } else { - std::vector arcs; - for (auto &it : wire_to_arcs[w]) { - arc_to_wires[it].erase(w); - arcs.push_back(it); - } - wire_to_arcs[w].clear(); - - ctx->sorted_shuffle(arcs); - - for (auto &it : arcs) - arc_queue_insert(it); - - if (ctx->debug) - log(" unbind wire %s\n", ctx->nameOfWire(w)); - - ctx->unbindWire(w); - wireScores[w]++; - } - - ripup_flag = true; - } - - void ripup_pip(PipId pip) - { - if (ctx->debug) - log(" ripup pip %s\n", ctx->nameOfPip(pip)); - - WireId w = ctx->getConflictingPipWire(pip); - - if (w == WireId()) { - NetInfo *n = ctx->getConflictingPipNet(pip); - if (n != nullptr) - ripup_net(n); - } else { - std::vector arcs; - for (auto &it : wire_to_arcs[w]) { - arc_to_wires[it].erase(w); - arcs.push_back(it); - } - wire_to_arcs[w].clear(); - - ctx->sorted_shuffle(arcs); - - for (auto &it : arcs) - arc_queue_insert(it); - - if (ctx->debug) - log(" unbind wire %s\n", ctx->nameOfWire(w)); - - ctx->unbindWire(w); - wireScores[w]++; - } - - ripup_flag = true; - } - - bool skip_net(NetInfo *net_info) - { -#ifdef ARCH_ECP5 - // ECP5 global nets currently appear part-unrouted due to arch database limitations - // Don't touch them in the router - if (net_info->is_global) - return true; -#endif - if (net_info->driver.cell == nullptr) - return true; - - return false; - } - - void check() - { - pool valid_arcs; - - for (auto &net_it : ctx->nets) { - NetInfo *net_info = net_it.second.get(); - pool valid_wires_for_net; - - if (skip_net(net_info)) - continue; - -#if 0 - if (ctx->debug) - log("[check] net: %s\n", ctx->nameOf(net_info)); -#endif - - auto src_wire = ctx->getNetinfoSourceWire(net_info); - log_assert(src_wire != WireId()); - - for (auto user : net_info->users.enumerate()) { - unsigned phys_idx = 0; - for (auto dst_wire : ctx->getNetinfoSinkWires(net_info, user.value)) { - log_assert(dst_wire != WireId()); - - arc_key arc; - arc.net_info = net_info; - arc.user_idx = user.index; - arc.phys_idx = phys_idx++; - valid_arcs.insert(arc); -#if 0 - if (ctx->debug) - log("[check] arc: %s %s\n", ctx->nameOfWire(src_wire), ctx->nameOfWire(dst_wire)); -#endif - - for (WireId wire : arc_to_wires[arc]) { -#if 0 - if (ctx->debug) - log("[check] wire: %s\n", ctx->nameOfWire(wire)); -#endif - valid_wires_for_net.insert(wire); - log_assert(wire_to_arcs[wire].count(arc)); - log_assert(net_info->wires.count(wire)); - } - } - } - - for (auto &it : net_info->wires) { - WireId w = it.first; - log_assert(valid_wires_for_net.count(w)); - } - } - - for (auto &it : wire_to_arcs) { - for (auto &arc : it.second) - log_assert(valid_arcs.count(arc)); - } - - for (auto &it : arc_to_wires) { - log_assert(valid_arcs.count(it.first)); - } - } - - void setup() - { - dict src_to_net; - dict dst_to_arc; - - std::vector net_names; - for (auto &net_it : ctx->nets) - net_names.push_back(net_it.first); - - ctx->sorted_shuffle(net_names); - - for (IdString net_name : net_names) { - NetInfo *net_info = ctx->nets.at(net_name).get(); - - if (skip_net(net_info)) - continue; - - auto src_wire = ctx->getNetinfoSourceWire(net_info); - - if (src_wire == WireId()) - log_error("No wire found for port %s on source cell %s.\n", ctx->nameOf(net_info->driver.port), - ctx->nameOf(net_info->driver.cell)); - - if (src_to_net.count(src_wire)) - log_error("Found two nets with same source wire %s: %s vs %s\n", ctx->nameOfWire(src_wire), - ctx->nameOf(net_info), ctx->nameOf(src_to_net.at(src_wire))); - - if (dst_to_arc.count(src_wire)) - log_error("Wire %s is used as source and sink in different nets: %s vs %s (%d)\n", - ctx->nameOfWire(src_wire), ctx->nameOf(net_info), - ctx->nameOf(dst_to_arc.at(src_wire).net_info), dst_to_arc.at(src_wire).user_idx.idx()); - - for (auto user : net_info->users.enumerate()) { - unsigned phys_idx = 0; - for (auto dst_wire : ctx->getNetinfoSinkWires(net_info, user.value)) { - arc_key arc; - arc.net_info = net_info; - arc.user_idx = user.index; - arc.phys_idx = phys_idx++; - - if (dst_to_arc.count(dst_wire)) { - if (dst_to_arc.at(dst_wire).net_info == net_info) - continue; - log_error("Found two arcs with same sink wire %s: %s (%d) vs %s (%d)\n", - ctx->nameOfWire(dst_wire), ctx->nameOf(net_info), user.index.idx(), - ctx->nameOf(dst_to_arc.at(dst_wire).net_info), - dst_to_arc.at(dst_wire).user_idx.idx()); - } - - if (src_to_net.count(dst_wire)) - log_error("Wire %s is used as source and sink in different nets: %s vs %s (%d)\n", - ctx->nameOfWire(dst_wire), ctx->nameOf(src_to_net.at(dst_wire)), - ctx->nameOf(net_info), user.index.idx()); - - dst_to_arc[dst_wire] = arc; - - if (net_info->wires.count(dst_wire) == 0) { - arc_queue_insert(arc, src_wire, dst_wire); - continue; - } - - WireId cursor = dst_wire; - wire_to_arcs[cursor].insert(arc); - arc_to_wires[arc].insert(cursor); - - while (src_wire != cursor) { - auto it = net_info->wires.find(cursor); - if (it == net_info->wires.end()) { - arc_queue_insert(arc, src_wire, dst_wire); - break; - } - - NPNR_ASSERT(it->second.pip != PipId()); - cursor = ctx->getPipSrcWire(it->second.pip); - wire_to_arcs[cursor].insert(arc); - arc_to_wires[arc].insert(cursor); - } - } - // TODO: this matches the situation before supporting multiple cell->bel pins, but do we want to keep - // this invariant? - if (phys_idx == 0) - log_warning("No wires found for port %s on destination cell %s.\n", ctx->nameOf(user.value.port), - ctx->nameOf(user.value.cell)); - } - - src_to_net[src_wire] = net_info; - - std::vector unbind_wires; - - for (auto &it : net_info->wires) - if (it.second.strength < STRENGTH_LOCKED && wire_to_arcs.count(it.first) == 0) - unbind_wires.push_back(it.first); - - for (auto it : unbind_wires) - ctx->unbindWire(it); - } - } - - bool route_arc(const arc_key &arc, bool ripup) - { - - NetInfo *net_info = arc.net_info; - auto user_idx = arc.user_idx; - - auto src_wire = ctx->getNetinfoSourceWire(net_info); - auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx], arc.phys_idx); - ripup_flag = false; - - float crit = tmg.get_criticality(CellPortKey(net_info->users.at(user_idx))); - - if (ctx->debug) { - log("Routing arc %d on net %s (%d arcs total):\n", user_idx.idx(), ctx->nameOf(net_info), - int(net_info->users.capacity())); - log(" source ... %s\n", ctx->nameOfWire(src_wire)); - log(" sink ..... %s\n", ctx->nameOfWire(dst_wire)); - } - - // unbind wires that are currently used exclusively by this arc - - pool old_arc_wires; - old_arc_wires.swap(arc_to_wires[arc]); - - for (WireId wire : old_arc_wires) { - auto &arc_wires = wire_to_arcs.at(wire); - NPNR_ASSERT(arc_wires.count(arc)); - arc_wires.erase(arc); - if (arc_wires.empty()) { - if (ctx->debug) - log(" unbind %s\n", ctx->nameOfWire(wire)); - ctx->unbindWire(wire); - } - } - - // special case - - if (src_wire == dst_wire) { - NetInfo *bound = ctx->getBoundWireNet(src_wire); - if (bound != nullptr) - NPNR_ASSERT(bound == net_info); - else { - ctx->bindWire(src_wire, net_info, STRENGTH_WEAK); - } - arc_to_wires[arc].insert(src_wire); - wire_to_arcs[src_wire].insert(arc); - return true; - } - - // reset wire queue - - if (!queue.empty()) { - std::priority_queue, QueuedWire::Greater> new_queue; - queue.swap(new_queue); - } - dict visited; - - // A* main loop - - int visitCnt = 0; - int maxVisitCnt = INT_MAX; - delay_t best_est = 0; - delay_t best_score = -1; - - { - QueuedWire qw; - qw.wire = src_wire; - qw.pip = PipId(); - qw.delay = ctx->getWireDelay(qw.wire).maxDelay(); - qw.penalty = 0; - qw.bonus = 0; - if (cfg.useEstimate) { - qw.togo = ctx->estimateDelay(qw.wire, dst_wire); - best_est = qw.delay + qw.togo; - } - qw.randtag = ctx->rng(); - - queue.push(qw); - visited[qw.wire] = qw; - } - - while (visitCnt++ < maxVisitCnt && !queue.empty()) { - QueuedWire qw = queue.top(); - queue.pop(); - - for (auto pip : ctx->getPipsDownhill(qw.wire)) { - delay_t next_delay = qw.delay + ctx->getPipDelay(pip).maxDelay(); - delay_t next_penalty = qw.penalty; - delay_t next_bonus = qw.bonus; - delay_t penalty_delta = 0; - - WireId next_wire = ctx->getPipDstWire(pip); - next_delay += ctx->getWireDelay(next_wire).maxDelay(); - - WireId conflictWireWire = WireId(), conflictPipWire = WireId(); - NetInfo *conflictWireNet = nullptr, *conflictPipNet = nullptr; - - if (net_info->wires.count(next_wire) && net_info->wires.at(next_wire).pip == pip) { - next_bonus += cfg.reuseBonus * (1.0 - crit); - } else { - if (!ctx->checkWireAvail(next_wire)) { - if (!ripup) - continue; - conflictWireWire = ctx->getConflictingWireWire(next_wire); - if (conflictWireWire == WireId()) { - conflictWireNet = ctx->getConflictingWireNet(next_wire); - if (conflictWireNet == nullptr) - continue; - else { - if (conflictWireNet->wires.count(next_wire) && - conflictWireNet->wires.at(next_wire).strength > STRENGTH_STRONG) - continue; - } - } else { - NetInfo *conflicting = ctx->getBoundWireNet(conflictWireWire); - if (conflicting != nullptr) { - if (conflicting->wires.count(conflictWireWire) && - conflicting->wires.at(conflictWireWire).strength > STRENGTH_STRONG) - continue; - } - } - } - - if (!ctx->checkPipAvail(pip)) { - if (!ripup) - continue; - conflictPipWire = ctx->getConflictingPipWire(pip); - if (conflictPipWire == WireId()) { - conflictPipNet = ctx->getConflictingPipNet(pip); - if (conflictPipNet == nullptr) - continue; - else { - if (conflictPipNet->wires.count(next_wire) && - conflictPipNet->wires.at(next_wire).strength > STRENGTH_STRONG) - continue; - } - } else { - NetInfo *conflicting = ctx->getBoundWireNet(conflictPipWire); - if (conflicting != nullptr) { - if (conflicting->wires.count(conflictPipWire) && - conflicting->wires.at(conflictPipWire).strength > STRENGTH_STRONG) - continue; - } - } - } - - if (conflictWireNet != nullptr && conflictPipWire != WireId() && - conflictWireNet->wires.count(conflictPipWire)) - conflictPipWire = WireId(); - - if (conflictPipNet != nullptr && conflictWireWire != WireId() && - conflictPipNet->wires.count(conflictWireWire)) - conflictWireWire = WireId(); - - if (conflictWireWire == conflictPipWire) - conflictWireWire = WireId(); - - if (conflictWireNet == conflictPipNet) - conflictWireNet = nullptr; - - if (conflictWireWire != WireId()) { - auto scores_it = wireScores.find(conflictWireWire); - if (scores_it != wireScores.end()) - penalty_delta += scores_it->second * cfg.wireRipupPenalty; - penalty_delta += cfg.wireRipupPenalty; - } - - if (conflictPipWire != WireId()) { - auto scores_it = wireScores.find(conflictPipWire); - if (scores_it != wireScores.end()) - penalty_delta += scores_it->second * cfg.wireRipupPenalty; - penalty_delta += cfg.wireRipupPenalty; - } - - if (conflictWireNet != nullptr) { - auto scores_it = netScores.find(conflictWireNet); - if (scores_it != netScores.end()) - penalty_delta += scores_it->second * cfg.netRipupPenalty; - penalty_delta += cfg.netRipupPenalty; - penalty_delta += conflictWireNet->wires.size() * cfg.wireRipupPenalty; - } - - if (conflictPipNet != nullptr) { - auto scores_it = netScores.find(conflictPipNet); - if (scores_it != netScores.end()) - penalty_delta += scores_it->second * cfg.netRipupPenalty; - penalty_delta += cfg.netRipupPenalty; - penalty_delta += conflictPipNet->wires.size() * cfg.wireRipupPenalty; - } - } - - next_penalty += penalty_delta * (timing_driven ? std::max(0.05, (1.0 - crit)) : 1); - - delay_t next_score = next_delay + next_penalty; - NPNR_ASSERT(next_score >= 0); - - if ((best_score >= 0) && (next_score - next_bonus - cfg.estimatePrecision > best_score)) - continue; - - auto old_visited_it = visited.find(next_wire); - if (old_visited_it != visited.end()) { - delay_t old_delay = old_visited_it->second.delay; - delay_t old_score = old_delay + old_visited_it->second.penalty; - NPNR_ASSERT(old_score >= 0); - - if (next_score + ctx->getDelayEpsilon() >= old_score) - continue; - -#if 0 - if (ctx->debug) - log("Found better route to %s. Old vs new delay estimate: %.3f (%.3f) %.3f (%.3f)\n", - ctx->nameOfWire(next_wire), - ctx->getDelayNS(old_score), - ctx->getDelayNS(old_visited_it->second.delay), - ctx->getDelayNS(next_score), - ctx->getDelayNS(next_delay)); -#endif - } - - QueuedWire next_qw; - next_qw.wire = next_wire; - next_qw.pip = pip; - next_qw.delay = next_delay; - next_qw.penalty = next_penalty; - next_qw.bonus = next_bonus; - if (cfg.useEstimate) { - next_qw.togo = ctx->estimateDelay(next_wire, dst_wire); - delay_t this_est = next_qw.delay + next_qw.togo; - if (this_est / 2 - cfg.estimatePrecision > best_est) - continue; - if (best_est > this_est) - best_est = this_est; - } - next_qw.randtag = ctx->rng(); - -#if 0 - if (ctx->debug) - log("%s -> %s: %.3f (%.3f)\n", - ctx->nameOfWire(qw.wire), - ctx->nameOfWire(next_wire), - ctx->getDelayNS(next_score), - ctx->getDelayNS(next_delay)); -#endif - - visited[next_qw.wire] = next_qw; - queue.push(next_qw); - - if (next_wire == dst_wire) { - maxVisitCnt = std::min(maxVisitCnt, 2 * visitCnt + (next_qw.penalty > 0 ? 100 : 0)); - best_score = next_score - next_bonus; - } - } - } - - if (ctx->debug) - log(" total number of visited nodes: %d\n", visitCnt); - - if (visited.count(dst_wire) == 0) { - if (ctx->debug) - log(" no route found for this arc\n"); - return false; - } - - if (ctx->debug) { - log(" final route delay: %8.2f\n", ctx->getDelayNS(visited[dst_wire].delay)); - log(" final route penalty: %8.2f\n", ctx->getDelayNS(visited[dst_wire].penalty)); - log(" final route bonus: %8.2f\n", ctx->getDelayNS(visited[dst_wire].bonus)); - log(" arc budget: %12.2f\n", ctx->getDelayNS(net_info->users[user_idx].budget)); - } - - // bind resulting route (and maybe unroute other nets) - - pool unassign_wires = arc_to_wires[arc]; - - WireId cursor = dst_wire; - delay_t accumulated_path_delay = 0; - delay_t last_path_delay_delta = 0; - while (1) { - auto pip = visited[cursor].pip; - - if (ctx->debug) { - delay_t path_delay_delta = ctx->estimateDelay(cursor, dst_wire) - accumulated_path_delay; - - log(" node %s (%+.2f %+.2f)\n", ctx->nameOfWire(cursor), ctx->getDelayNS(path_delay_delta), - ctx->getDelayNS(path_delay_delta - last_path_delay_delta)); - - last_path_delay_delta = path_delay_delta; - - if (pip != PipId()) - accumulated_path_delay += ctx->getPipDelay(pip).maxDelay(); - accumulated_path_delay += ctx->getWireDelay(cursor).maxDelay(); - } - - if (pip == PipId()) - NPNR_ASSERT(cursor == src_wire); - - if (!net_info->wires.count(cursor) || net_info->wires.at(cursor).pip != pip) { - if (!ctx->checkWireAvail(cursor)) { - ripup_wire(cursor); - NPNR_ASSERT(ctx->checkWireAvail(cursor)); - } - - if (pip != PipId() && !ctx->checkPipAvail(pip)) { - ripup_pip(pip); - NPNR_ASSERT(ctx->checkPipAvail(pip)); - } - - if (pip == PipId()) { - if (ctx->debug) - log(" bind wire %s\n", ctx->nameOfWire(cursor)); - ctx->bindWire(cursor, net_info, STRENGTH_WEAK); - } else { - if (ctx->debug) - log(" bind pip %s\n", ctx->nameOfPip(pip)); - ctx->bindPip(pip, net_info, STRENGTH_WEAK); - } - } - - wire_to_arcs[cursor].insert(arc); - arc_to_wires[arc].insert(cursor); - - if (pip == PipId()) - break; - - cursor = ctx->getPipSrcWire(pip); - } - - if (ripup_flag) - arcs_with_ripup++; - else - arcs_without_ripup++; - - return true; - } - - delay_t find_slack_thresh() - { - // If more than 5% of arcs have negative slack; use the 5% threshold as a ripup criteria - int arc_count = 0; - int failed_count = 0; - delay_t default_thresh = ctx->getDelayEpsilon(); - - for (auto &net : ctx->nets) { - NetInfo *ni = net.second.get(); - if (skip_net(ni)) - continue; - for (auto &usr : ni->users) { - ++arc_count; - delay_t slack = tmg.get_setup_slack(CellPortKey(usr)); - if (slack == std::numeric_limits::min()) - continue; - if (slack < default_thresh) - ++failed_count; - } - } - - if (arc_count < 50 || (failed_count < (0.05 * arc_count))) { - return default_thresh; - } - - std::vector slacks; - for (auto &net : ctx->nets) { - NetInfo *ni = net.second.get(); - if (skip_net(ni)) - continue; - for (auto &usr : ni->users) { - delay_t slack = tmg.get_setup_slack(CellPortKey(usr)); - if (slack == std::numeric_limits::min()) - continue; - slacks.push_back(slack); - } - } - std::sort(slacks.begin(), slacks.end()); - delay_t thresh = slacks.at(int(slacks.size() * 0.05)); - log_warning("%.f%% of arcs have failing slack; using %.2fns as ripup threshold. Consider a reduced Fmax " - "constraint.\n", - (100.0 * failed_count) / arc_count, ctx->getDelayNS(thresh)); - return thresh; - } -}; - -} // namespace - -NEXTPNR_NAMESPACE_BEGIN - -Router1Cfg::Router1Cfg(Context *ctx) -{ - maxIterCnt = ctx->setting("router1/maxIterCnt", 200); - cleanupReroute = ctx->setting("router1/cleanupReroute", true); - fullCleanupReroute = ctx->setting("router1/fullCleanupReroute", true); - useEstimate = ctx->setting("router1/useEstimate", true); - - wireRipupPenalty = ctx->getRipupDelayPenalty(); - netRipupPenalty = 10 * ctx->getRipupDelayPenalty(); - reuseBonus = wireRipupPenalty / 2; - - estimatePrecision = 100 * ctx->getRipupDelayPenalty(); -} - -bool router1(Context *ctx, const Router1Cfg &cfg) -{ - try { - log_break(); - log_info("Routing..\n"); - ScopeLock lock(ctx); - auto rstart = std::chrono::high_resolution_clock::now(); - - log_info("Setting up routing queue.\n"); - - Router1 router(ctx, cfg); - router.setup(); -#ifndef NDEBUG - router.check(); -#endif - - log_info("Routing %d arcs.\n", int(router.arc_queue.size())); - - int iter_cnt = 0; - int last_arcs_with_ripup = 0; - int last_arcs_without_ripup = 0; - int timing_fail_count = 0; - bool timing_ripup = ctx->setting("router/tmg_ripup", false); - delay_t ripup_slack = 0; - - log_info(" | (re-)routed arcs | delta | remaining| time spent |\n"); - log_info(" IterCnt | w/ripup wo/ripup | w/r wo/r | arcs| batch(sec) total(sec)|\n"); - - auto prev_time = rstart; - while (!router.arc_queue.empty()) { - if (++iter_cnt % 1000 == 0) { - auto curr_time = std::chrono::high_resolution_clock::now(); - log_info("%10d | %8d %10d | %4d %5d | %9d| %10.02f %10.02f|\n", iter_cnt, router.arcs_with_ripup, - router.arcs_without_ripup, router.arcs_with_ripup - last_arcs_with_ripup, - router.arcs_without_ripup - last_arcs_without_ripup, int(router.arc_queue.size()), - std::chrono::duration(curr_time - prev_time).count(), - std::chrono::duration(curr_time - rstart).count()); - prev_time = curr_time; - last_arcs_with_ripup = router.arcs_with_ripup; - last_arcs_without_ripup = router.arcs_without_ripup; - ctx->yield(); -#ifndef NDEBUG - router.check(); -#endif - } - - if (ctx->debug) - log("-- %d --\n", iter_cnt); - - arc_key arc = router.arc_queue_pop(); - - if (!router.route_arc(arc, true)) { - log_warning("Failed to find a route for arc %d of net %s.\n", arc.user_idx.idx(), - ctx->nameOf(arc.net_info)); -#ifndef NDEBUG - router.check(); - ctx->check(); -#endif - return false; - } - // Timing driven ripup - if (timing_ripup && router.arc_queue.empty() && timing_fail_count < 50) { - ++timing_fail_count; - router.tmg.run(); - delay_t wns = 0, tns = 0; - if (timing_fail_count == 1) - ripup_slack = router.find_slack_thresh(); - for (auto &net : ctx->nets) { - NetInfo *ni = net.second.get(); - if (router.skip_net(ni)) - continue; - bool is_locked = false; - for (auto &wire : ni->wires) { - if (wire.second.strength > STRENGTH_STRONG) - is_locked = true; - } - if (is_locked) - continue; - for (auto &usr : ni->users) { - delay_t slack = router.tmg.get_setup_slack(CellPortKey(usr)); - if (slack == std::numeric_limits::min()) - continue; - if (slack < 0) { - wns = std::min(wns, slack); - tns += slack; - } - if (slack <= ripup_slack) { - for (WireId w : ctx->getNetinfoSinkWires(ni, usr)) { - if (ctx->checkWireAvail(w)) - continue; - router.ripup_wire(w); - } - } - } - } - log_info(" %d arcs ripped up due to negative slack WNS=%.02fns TNS=%.02fns.\n", - int(router.arc_queue.size()), ctx->getDelayNS(wns), ctx->getDelayNS(tns)); - iter_cnt = 0; - router.wireScores.clear(); - router.netScores.clear(); - } - } - auto rend = std::chrono::high_resolution_clock::now(); - log_info("%10d | %8d %10d | %4d %5d | %9d| %10.02f %10.02f|\n", iter_cnt, router.arcs_with_ripup, - router.arcs_without_ripup, router.arcs_with_ripup - last_arcs_with_ripup, - router.arcs_without_ripup - last_arcs_without_ripup, int(router.arc_queue.size()), - std::chrono::duration(rend - prev_time).count(), - std::chrono::duration(rend - rstart).count()); - log_info("Routing complete.\n"); - ctx->yield(); - log_info("Router1 time %.02fs\n", std::chrono::duration(rend - rstart).count()); - -#ifndef NDEBUG - router.check(); - ctx->check(); - log_assert(ctx->checkRoutedDesign()); -#endif - - log_info("Checksum: 0x%08x\n", ctx->checksum()); - timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */, - true /* warn_on_failure */, true /* update_results */); - - return true; - } catch (log_execution_error_exception) { -#ifndef NDEBUG - ctx->lock(); - ctx->check(); - ctx->unlock(); -#endif - return false; - } -} - -bool Context::checkRoutedDesign() const -{ - const Context *ctx = getCtx(); - - for (auto &net_it : ctx->nets) { - NetInfo *net_info = net_it.second.get(); - -#ifdef ARCH_ECP5 - if (net_info->is_global) - continue; -#endif - - if (ctx->debug) - log("checking net %s\n", ctx->nameOf(net_info)); - - if (net_info->users.empty()) { - if (ctx->debug) - log(" net without sinks\n"); - log_assert(net_info->wires.empty()); - continue; - } - - bool found_unrouted = false; - bool found_loop = false; - bool found_stub = false; - - struct ExtraWireInfo - { - int order_num = 0; - pool children; - }; - - dict> db; - - for (auto &it : net_info->wires) { - WireId w = it.first; - PipId p = it.second.pip; - - if (p != PipId()) { - log_assert(ctx->getPipDstWire(p) == w); - db.emplace(ctx->getPipSrcWire(p), std::make_unique()).first->second->children.insert(w); - } - } - - auto src_wire = ctx->getNetinfoSourceWire(net_info); - if (src_wire == WireId()) { - log_assert(net_info->driver.cell == nullptr); - if (ctx->debug) - log(" undriven and unrouted\n"); - continue; - } - - if (net_info->wires.count(src_wire) == 0) { - if (ctx->debug) - log(" source (%s) not bound to net\n", ctx->nameOfWire(src_wire)); - found_unrouted = true; - } - - dict> dest_wires; - for (auto user : net_info->users.enumerate()) { - for (auto dst_wire : ctx->getNetinfoSinkWires(net_info, user.value)) { - log_assert(dst_wire != WireId()); - dest_wires[dst_wire] = user.index; - - if (net_info->wires.count(dst_wire) == 0) { - if (ctx->debug) - log(" sink %d (%s) not bound to net\n", user.index.idx(), ctx->nameOfWire(dst_wire)); - found_unrouted = true; - } - } - } - - std::function setOrderNum; - pool logged_wires; - - setOrderNum = [&](WireId w, int num) { - auto &db_entry = *db.emplace(w, std::make_unique()).first->second; - if (db_entry.order_num != 0) { - found_loop = true; - log(" %*s=> loop\n", 2 * num, ""); - return; - } - db_entry.order_num = num; - for (WireId child : db_entry.children) { - if (ctx->debug) { - log(" %*s-> %s\n", 2 * num, "", ctx->nameOfWire(child)); - logged_wires.insert(child); - } - setOrderNum(child, num + 1); - } - if (db_entry.children.empty()) { - if (dest_wires.count(w) != 0) { - if (ctx->debug) - log(" %*s=> sink %d\n", 2 * num, "", dest_wires.at(w).idx()); - } else { - if (ctx->debug) - log(" %*s=> stub\n", 2 * num, ""); - found_stub = true; - } - } - }; - - if (ctx->debug) { - log(" driver: %s\n", ctx->nameOfWire(src_wire)); - logged_wires.insert(src_wire); - } - setOrderNum(src_wire, 1); - - pool dangling_wires; - - for (auto &it : db) { - auto &db_entry = *it.second; - if (db_entry.order_num == 0) - dangling_wires.insert(it.first); - } - - if (ctx->debug) { - if (dangling_wires.empty()) { - log(" no dangling wires.\n"); - } else { - pool root_wires = dangling_wires; - - for (WireId w : dangling_wires) { - for (WireId c : db[w]->children) - root_wires.erase(c); - } - - for (WireId w : root_wires) { - log(" dangling wire: %s\n", ctx->nameOfWire(w)); - logged_wires.insert(w); - setOrderNum(w, 1); - } - - for (WireId w : dangling_wires) { - if (logged_wires.count(w) == 0) - log(" loop: %s -> %s\n", ctx->nameOfWire(ctx->getPipSrcWire(net_info->wires.at(w).pip)), - ctx->nameOfWire(w)); - } - } - } - - bool fail = false; - - if (found_unrouted) { - if (ctx->debug) - log("check failed: found unrouted arcs\n"); - fail = true; - } - - if (found_loop) { - if (ctx->debug) - log("check failed: found loops\n"); - fail = true; - } - - if (found_stub) { - if (ctx->debug) - log("check failed: found stubs\n"); - fail = true; - } - - if (!dangling_wires.empty()) { - if (ctx->debug) - log("check failed: found dangling wires\n"); - fail = true; - } - - if (fail) - return false; - } - - return true; -} - -bool Context::getActualRouteDelay(WireId src_wire, WireId dst_wire, delay_t *delay, dict *route, - bool useEstimate) -{ - // FIXME - return false; -} - -NEXTPNR_NAMESPACE_END diff --git a/common/router1.h b/common/router1.h deleted file mode 100644 index a7ec5bad..00000000 --- a/common/router1.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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 ROUTER1_H -#define ROUTER1_H - -#include "log.h" -#include "nextpnr.h" -NEXTPNR_NAMESPACE_BEGIN - -struct Router1Cfg -{ - Router1Cfg(Context *ctx); - - int maxIterCnt; - bool cleanupReroute; - bool fullCleanupReroute; - bool useEstimate; - delay_t wireRipupPenalty; - delay_t netRipupPenalty; - delay_t reuseBonus; - delay_t estimatePrecision; -}; - -extern bool router1(Context *ctx, const Router1Cfg &cfg); - -NEXTPNR_NAMESPACE_END - -#endif // ROUTER1_H diff --git a/common/router2.cc b/common/router2.cc deleted file mode 100644 index e943e493..00000000 --- a/common/router2.cc +++ /dev/null @@ -1,1499 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2019 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. - * - * Core routing algorithm based on CRoute: - * - * CRoute: A Fast High-quality Timing-driven Connection-based FPGA Router - * Dries Vercruyce, Elias Vansteenkiste and Dirk Stroobandt - * DOI 10.1109/FCCM.2019.00017 [PDF on SciHub] - * - * Modified for the nextpnr Arch API and data structures; optimised for - * real-world FPGA architectures in particular ECP5 and Xilinx UltraScale+ - * - */ - -#include "router2.h" - -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "nextpnr.h" -#include "router1.h" -#include "scope_lock.h" -#include "timing.h" -#include "util.h" - -NEXTPNR_NAMESPACE_BEGIN - -namespace { -struct Router2 -{ - - struct PerArcData - { - WireId sink_wire; - ArcBounds bb; - bool routed = false; - }; - - // As we allow overlap at first; the nextpnr bind functions can't be used - // as the primary relation between arcs and wires/pips - struct PerNetData - { - WireId src_wire; - dict> wires; - std::vector> arcs; - ArcBounds bb; - // Coordinates of the center of the net, used for the weight-to-average - int cx, cy, hpwl; - int total_route_us = 0; - float max_crit = 0; - int fail_count = 0; - }; - - struct WireScore - { - float cost; - float togo_cost; - float total() const { return cost + togo_cost; } - }; - - struct PerWireData - { - // nextpnr - WireId w; - // Historical congestion cost - int curr_cong = 0; - float hist_cong_cost = 1.0; - // Wire is unavailable as locked to another arc - bool unavailable = false; - // This wire has to be used for this net - int reserved_net = -1; - // The notional location of the wire, to guarantee thread safety - int16_t x = 0, y = 0; - // Visit data - PipId pip_fwd, pip_bwd; - bool visited_fwd = false, visited_bwd = false; - }; - - Context *ctx; - Router2Cfg cfg; - - Router2(Context *ctx, const Router2Cfg &cfg) : ctx(ctx), cfg(cfg), tmg(ctx) { tmg.setup(); } - - // Use 'udata' for fast net lookups and indexing - std::vector nets_by_udata; - std::vector nets; - - bool timing_driven, timing_driven_ripup; - TimingAnalyser tmg; - - void setup_nets() - { - // Populate per-net and per-arc structures at start of routing - nets.resize(ctx->nets.size()); - nets_by_udata.resize(ctx->nets.size()); - size_t i = 0; - for (auto &net : ctx->nets) { - NetInfo *ni = net.second.get(); - ni->udata = i; - nets_by_udata.at(i) = ni; - nets.at(i).arcs.resize(ni->users.capacity()); - - // Start net bounding box at overall min/max - nets.at(i).bb.x0 = std::numeric_limits::max(); - nets.at(i).bb.x1 = std::numeric_limits::min(); - nets.at(i).bb.y0 = std::numeric_limits::max(); - nets.at(i).bb.y1 = std::numeric_limits::min(); - nets.at(i).cx = 0; - nets.at(i).cy = 0; - - if (ni->driver.cell != nullptr) { - Loc drv_loc = ctx->getBelLocation(ni->driver.cell->bel); - nets.at(i).cx += drv_loc.x; - nets.at(i).cy += drv_loc.y; - } - - for (auto usr : ni->users.enumerate()) { - WireId src_wire = ctx->getNetinfoSourceWire(ni); - for (auto &dst_wire : ctx->getNetinfoSinkWires(ni, usr.value)) { - nets.at(i).src_wire = src_wire; - if (ni->driver.cell == nullptr) - src_wire = dst_wire; - if (ni->driver.cell == nullptr && dst_wire == WireId()) - continue; - if (src_wire == WireId()) - log_error("No wire found for port %s on source cell %s.\n", ctx->nameOf(ni->driver.port), - ctx->nameOf(ni->driver.cell)); - if (dst_wire == WireId()) - log_error("No wire found for port %s on destination cell %s.\n", ctx->nameOf(usr.value.port), - ctx->nameOf(usr.value.cell)); - nets.at(i).arcs.at(usr.index.idx()).emplace_back(); - auto &ad = nets.at(i).arcs.at(usr.index.idx()).back(); - ad.sink_wire = dst_wire; - // Set bounding box for this arc - ad.bb = ctx->getRouteBoundingBox(src_wire, dst_wire); - // Expand net bounding box to include this arc - nets.at(i).bb.x0 = std::min(nets.at(i).bb.x0, ad.bb.x0); - nets.at(i).bb.x1 = std::max(nets.at(i).bb.x1, ad.bb.x1); - nets.at(i).bb.y0 = std::min(nets.at(i).bb.y0, ad.bb.y0); - nets.at(i).bb.y1 = std::max(nets.at(i).bb.y1, ad.bb.y1); - } - // Add location to centroid sum - Loc usr_loc = ctx->getBelLocation(usr.value.cell->bel); - nets.at(i).cx += usr_loc.x; - nets.at(i).cy += usr_loc.y; - } - nets.at(i).hpwl = std::max( - std::abs(nets.at(i).bb.y1 - nets.at(i).bb.y0) + std::abs(nets.at(i).bb.x1 - nets.at(i).bb.x0), 1); - nets.at(i).cx /= int(ni->users.entries() + 1); - nets.at(i).cy /= int(ni->users.entries() + 1); - if (ctx->debug) - log_info("%s: bb=(%d, %d)->(%d, %d) c=(%d, %d) hpwl=%d\n", ctx->nameOf(ni), nets.at(i).bb.x0, - nets.at(i).bb.y0, nets.at(i).bb.x1, nets.at(i).bb.y1, nets.at(i).cx, nets.at(i).cy, - nets.at(i).hpwl); - nets.at(i).bb.x0 = std::max(nets.at(i).bb.x0 - cfg.bb_margin_x, 0); - nets.at(i).bb.y0 = std::max(nets.at(i).bb.y0 - cfg.bb_margin_y, 0); - nets.at(i).bb.x1 = std::min(nets.at(i).bb.x1 + cfg.bb_margin_x, ctx->getGridDimX()); - nets.at(i).bb.y1 = std::min(nets.at(i).bb.y1 + cfg.bb_margin_y, ctx->getGridDimY()); - i++; - } - } - - dict wire_to_idx; - std::vector flat_wires; - - PerWireData &wire_data(WireId w) { return flat_wires[wire_to_idx.at(w)]; } - - void setup_wires() - { - // Set up per-wire structures, so that MT parts don't have to do any memory allocation - // This is possibly quite wasteful and not cache-optimal; further consideration necessary - for (auto wire : ctx->getWires()) { - PerWireData pwd; - pwd.w = wire; - NetInfo *bound = ctx->getBoundWireNet(wire); - if (bound != nullptr) { - auto iter = bound->wires.find(wire); - if (iter != bound->wires.end()) { - auto &nd = nets.at(bound->udata); - nd.wires[wire] = std::make_pair(bound->wires.at(wire).pip, 0); - pwd.curr_cong = 1; - if (bound->wires.at(wire).strength == STRENGTH_PLACER) { - pwd.reserved_net = bound->udata; - } else if (bound->wires.at(wire).strength > STRENGTH_PLACER) { - pwd.unavailable = true; - } - } - } - - ArcBounds wire_loc = ctx->getRouteBoundingBox(wire, wire); - pwd.x = (wire_loc.x0 + wire_loc.x1) / 2; - pwd.y = (wire_loc.y0 + wire_loc.y1) / 2; - - wire_to_idx[wire] = int(flat_wires.size()); - flat_wires.push_back(pwd); - } - - for (auto &net_pair : ctx->nets) { - auto *net = net_pair.second.get(); - auto &nd = nets.at(net->udata); - for (auto usr : net->users.enumerate()) { - auto &ad = nd.arcs.at(usr.index.idx()); - for (size_t phys_pin = 0; phys_pin < ad.size(); phys_pin++) { - if (check_arc_routing(net, usr.index, phys_pin)) { - record_prerouted_net(net, usr.index, phys_pin); - } - } - } - } - } - - struct QueuedWire - { - - explicit QueuedWire(int wire = -1, WireScore score = WireScore{}, int randtag = 0) - : wire(wire), score(score), randtag(randtag){}; - - int wire; - WireScore score; - int randtag = 0; - - struct Greater - { - bool operator()(const QueuedWire &lhs, const QueuedWire &rhs) const noexcept - { - float lhs_score = lhs.score.cost + lhs.score.togo_cost, - rhs_score = rhs.score.cost + rhs.score.togo_cost; - return lhs_score == rhs_score ? lhs.randtag > rhs.randtag : lhs_score > rhs_score; - } - }; - }; - - bool hit_test_pip(ArcBounds &bb, Loc l) { return l.x >= bb.x0 && l.x <= bb.x1 && l.y >= bb.y0 && l.y <= bb.y1; } - - double curr_cong_weight, hist_cong_weight, estimate_weight; - - struct ThreadContext - { - // Nets to route - std::vector route_nets; - // Nets that failed routing - std::vector failed_nets; - - std::vector, size_t>> route_arcs; - - std::priority_queue, QueuedWire::Greater> fwd_queue, bwd_queue; - // Special case where one net has multiple logical arcs to the same physical sink - pool processed_sinks; - - std::vector dirty_wires; - - // Thread bounding box - ArcBounds bb; - - DeterministicRNG rng; - - // Used to add existing routing to the heap - pool in_wire_by_loc; - dict, pool> wire_by_loc; - }; - - bool thread_test_wire(ThreadContext &t, PerWireData &w) - { - return w.x >= t.bb.x0 && w.x <= t.bb.x1 && w.y >= t.bb.y0 && w.y <= t.bb.y1; - } - - enum ArcRouteResult - { - ARC_SUCCESS, - ARC_RETRY_WITHOUT_BB, - ARC_FATAL, - }; - -// Define to make sure we don't print in a multithreaded context -#define ARC_LOG_ERR(...) \ - do { \ - if (is_mt) \ - return ARC_FATAL; \ - else \ - log_error(__VA_ARGS__); \ - } while (0) -#define ROUTE_LOG_DBG(...) \ - do { \ - if (!is_mt && ctx->debug) \ - log(__VA_ARGS__); \ - } while (0) - - void bind_pip_internal(PerNetData &net, store_index user, int wire, PipId pip) - { - auto &wd = flat_wires.at(wire); - auto found = net.wires.find(wd.w); - if (found == net.wires.end()) { - // Not yet used for any arcs of this net, add to list - net.wires.emplace(wd.w, std::make_pair(pip, 1)); - // Increase bound count of wire by 1 - ++wd.curr_cong; - } else { - // Already used for at least one other arc of this net - // Don't allow two uphill PIPs for the same net and wire - NPNR_ASSERT(found->second.first == pip); - // Increase the count of bound arcs - ++found->second.second; - } - } - - void unbind_pip_internal(PerNetData &net, store_index user, WireId wire) - { - auto &wd = wire_data(wire); - auto &b = net.wires.at(wd.w); - --b.second; - if (b.second == 0) { - // No remaining arcs of this net bound to this wire - --wd.curr_cong; - net.wires.erase(wd.w); - } - } - - void ripup_arc(NetInfo *net, store_index user, size_t phys_pin) - { - auto &nd = nets.at(net->udata); - auto &ad = nd.arcs.at(user.idx()).at(phys_pin); - if (!ad.routed) - return; - WireId src = nets.at(net->udata).src_wire; - WireId cursor = ad.sink_wire; - while (cursor != src) { - PipId pip = nd.wires.at(cursor).first; - unbind_pip_internal(nd, user, cursor); - cursor = ctx->getPipSrcWire(pip); - } - ad.routed = false; - } - - float score_wire_for_arc(NetInfo *net, store_index user, size_t phys_pin, WireId wire, PipId pip, - float crit_weight) - { - auto &wd = wire_data(wire); - auto &nd = nets.at(net->udata); - float base_cost = cfg.get_base_cost(ctx, wire, pip, crit_weight); - int overuse = wd.curr_cong; - float hist_cost = 1.0f + crit_weight * (wd.hist_cong_cost - 1.0f); - float bias_cost = 0; - int source_uses = 0; - if (nd.wires.count(wire)) { - overuse -= 1; - source_uses = nd.wires.at(wire).second; - } - float present_cost = 1.0f + overuse * curr_cong_weight * crit_weight; - if (pip != PipId()) { - Loc pl = ctx->getPipLocation(pip); - bias_cost = cfg.bias_cost_factor * (base_cost / int(net->users.entries())) * - ((std::abs(pl.x - nd.cx) + std::abs(pl.y - nd.cy)) / float(nd.hpwl)); - } - return base_cost * hist_cost * present_cost / (1 + (source_uses * crit_weight)) + bias_cost; - } - - float get_togo_cost(NetInfo *net, store_index user, int wire, WireId src_sink, float crit_weight, - bool bwd = false) - { - auto &nd = nets.at(net->udata); - auto &wd = flat_wires[wire]; - int source_uses = 0; - if (nd.wires.count(wd.w)) { - source_uses = nd.wires.at(wd.w).second; - } - // FIXME: timing/wirelength balance? - delay_t est_delay = ctx->estimateDelay(bwd ? src_sink : wd.w, bwd ? wd.w : src_sink); - return (ctx->getDelayNS(est_delay) / (1 + source_uses * crit_weight)) + cfg.ipin_cost_adder; - } - - bool check_arc_routing(NetInfo *net, store_index usr, size_t phys_pin) - { - auto &nd = nets.at(net->udata); - auto &ad = nd.arcs.at(usr.idx()).at(phys_pin); - WireId src_wire = nets.at(net->udata).src_wire; - WireId cursor = ad.sink_wire; - while (nd.wires.count(cursor)) { - auto &wd = wire_data(cursor); - if (wd.curr_cong != 1) - return false; - auto &uh = nd.wires.at(cursor).first; - if (uh == PipId()) - break; - cursor = ctx->getPipSrcWire(uh); - } - return (cursor == src_wire); - } - - void record_prerouted_net(NetInfo *net, store_index usr, size_t phys_pin) - { - auto &nd = nets.at(net->udata); - auto &ad = nd.arcs.at(usr.idx()).at(phys_pin); - ad.routed = true; - - WireId src = nets.at(net->udata).src_wire; - WireId cursor = ad.sink_wire; - while (cursor != src) { - size_t wire_idx = wire_to_idx.at(cursor); - PipId pip = nd.wires.at(cursor).first; - bind_pip_internal(nd, usr, wire_idx, pip); - cursor = ctx->getPipSrcWire(pip); - } - } - - // Returns true if a wire contains no source ports or driving pips - bool is_wire_undriveable(WireId wire, const NetInfo *net, int iter_count = 0) - { - // This is specifically designed to handle a particularly icky case that the current router struggles with in - // the nexus device, - // C -> C lut input only - // C; D; or F from another lut -> D lut input - // D or M -> M ff input - // without careful reservation of C for C lut input and D for D lut input, there is fighting for D between FF - // and LUT - if (iter_count > 7) - return false; // heuristic to assume we've hit general routing - if (wire_data(wire).unavailable) - return true; - if (wire_data(wire).reserved_net != -1 && wire_data(wire).reserved_net != net->udata) - return true; // reserved for another net - for (auto bp : ctx->getWireBelPins(wire)) - if ((net->driver.cell == nullptr || bp.bel == net->driver.cell->bel) && - ctx->getBelPinType(bp.bel, bp.pin) != PORT_IN) - return false; - for (auto p : ctx->getPipsUphill(wire)) - if (ctx->checkPipAvail(p)) { - if (!is_wire_undriveable(ctx->getPipSrcWire(p), net, iter_count + 1)) - return false; - } - return true; - } - - // Find all the wires that must be used to route a given arc - bool reserve_wires_for_arc(NetInfo *net, store_index i) - { - bool did_something = false; - WireId src = ctx->getNetinfoSourceWire(net); - auto &usr = net->users.at(i); - for (auto sink : ctx->getNetinfoSinkWires(net, usr)) { - pool rsv; - WireId cursor = sink; - bool done = false; - if (ctx->debug) - log("reserving wires for arc %d (%s.%s) of net %s\n", i.idx(), ctx->nameOf(usr.cell), - ctx->nameOf(usr.port), ctx->nameOf(net)); - while (!done) { - auto &wd = wire_data(cursor); - if (ctx->debug) - log(" %s\n", ctx->nameOfWire(cursor)); - did_something |= (wd.reserved_net != net->udata); - if (wd.reserved_net != -1 && wd.reserved_net != net->udata) - log_error("attempting to reserve wire '%s' for nets '%s' and '%s'\n", ctx->nameOfWire(cursor), - ctx->nameOf(nets_by_udata.at(wd.reserved_net)), ctx->nameOf(net)); - wd.reserved_net = net->udata; - if (cursor == src) - break; - WireId next_cursor; - for (auto uh : ctx->getPipsUphill(cursor)) { - WireId w = ctx->getPipSrcWire(uh); - if (is_wire_undriveable(w, net)) - continue; - if (next_cursor != WireId()) { - done = true; - break; - } - next_cursor = w; - } - if (next_cursor == WireId()) - break; - cursor = next_cursor; - } - } - return did_something; - } - - void find_all_reserved_wires() - { - // Run iteratively, as reserving wires for one net might limit choices for another - bool did_something = false; - do { - did_something = false; - for (auto net : nets_by_udata) { - WireId src = ctx->getNetinfoSourceWire(net); - if (src == WireId()) - continue; - for (auto usr : net->users.enumerate()) - did_something |= reserve_wires_for_arc(net, usr.index); - } - } while (did_something); - } - - void reset_wires(ThreadContext &t) - { - for (auto w : t.dirty_wires) { - flat_wires[w].pip_fwd = PipId(); - flat_wires[w].pip_bwd = PipId(); - flat_wires[w].visited_fwd = false; - flat_wires[w].visited_bwd = false; - } - t.dirty_wires.clear(); - } - - // These nets have very-high-fanout pips and special rules must be followed (only working backwards) to avoid - // crippling perf - bool is_pseudo_const_net(const NetInfo *net) - { -#ifdef ARCH_NEXUS - if (net->driver.cell != nullptr && net->driver.cell->type == id_VCC_DRV) - return true; -#endif - return false; - } - - void update_wire_by_loc(ThreadContext &t, NetInfo *net, store_index i, size_t phys_pin, bool is_mt) - { - if (is_pseudo_const_net(net)) - return; - auto &nd = nets.at(net->udata); - auto &ad = nd.arcs.at(i.idx()).at(phys_pin); - WireId cursor = ad.sink_wire; - if (!nd.wires.count(cursor)) - return; - while (cursor != nd.src_wire) { - if (!t.in_wire_by_loc.count(cursor)) { - t.in_wire_by_loc.insert(cursor); - for (auto dh : ctx->getPipsDownhill(cursor)) { - Loc dh_loc = ctx->getPipLocation(dh); - t.wire_by_loc[std::make_pair(dh_loc.x, dh_loc.y)].insert(cursor); - } - } - cursor = ctx->getPipSrcWire(nd.wires.at(cursor).first); - } - } - - // Functions for marking wires as visited, and checking if they have already been visited - void set_visited_fwd(ThreadContext &t, int wire, PipId pip) - { - auto &wd = flat_wires.at(wire); - if (!wd.visited_fwd && !wd.visited_bwd) - t.dirty_wires.push_back(wire); - wd.pip_fwd = pip; - wd.visited_fwd = true; - } - void set_visited_bwd(ThreadContext &t, int wire, PipId pip) - { - auto &wd = flat_wires.at(wire); - if (!wd.visited_fwd && !wd.visited_bwd) - t.dirty_wires.push_back(wire); - wd.pip_bwd = pip; - wd.visited_bwd = true; - } - - bool was_visited_fwd(int wire) { return flat_wires.at(wire).visited_fwd; } - bool was_visited_bwd(int wire) { return flat_wires.at(wire).visited_bwd; } - - float get_arc_crit(NetInfo *net, store_index i) - { - if (!timing_driven) - return 0; - return tmg.get_criticality(CellPortKey(net->users.at(i))); - } - - bool arc_failed_slack(NetInfo *net, store_index usr_idx) - { - return timing_driven_ripup && - (tmg.get_setup_slack(CellPortKey(net->users.at(usr_idx))) < (2 * ctx->getDelayEpsilon())); - } - - ArcRouteResult route_arc(ThreadContext &t, NetInfo *net, store_index i, size_t phys_pin, bool is_mt, - bool is_bb = true) - { - // Do some initial lookups and checks - auto arc_start = std::chrono::high_resolution_clock::now(); - auto &nd = nets[net->udata]; - auto &ad = nd.arcs.at(i.idx()).at(phys_pin); - auto &usr = net->users.at(i); - ROUTE_LOG_DBG("Routing arc %d of net '%s' (%d, %d) -> (%d, %d)\n", i.idx(), ctx->nameOf(net), ad.bb.x0, - ad.bb.y0, ad.bb.x1, ad.bb.y1); - WireId src_wire = ctx->getNetinfoSourceWire(net), dst_wire = ctx->getNetinfoSinkWire(net, usr, phys_pin); - if (src_wire == WireId()) - ARC_LOG_ERR("No wire found for port %s on source cell %s.\n", ctx->nameOf(net->driver.port), - ctx->nameOf(net->driver.cell)); - if (dst_wire == WireId()) - ARC_LOG_ERR("No wire found for port %s on destination cell %s.\n", ctx->nameOf(usr.port), - ctx->nameOf(usr.cell)); - int src_wire_idx = wire_to_idx.at(src_wire); - int dst_wire_idx = wire_to_idx.at(dst_wire); - // Calculate a timing weight based on criticality - float crit = get_arc_crit(net, i); - float crit_weight = (1.0f - std::pow(crit, 2)); - ROUTE_LOG_DBG(" crit=%.3f crit_weight=%.3f\n", crit, crit_weight); - // Check if arc was already done _in this iteration_ - if (t.processed_sinks.count(dst_wire)) - return ARC_SUCCESS; - - // We have two modes: - // 0. starting within a small range of existing routing - // 1. expanding from all routing - int mode = 0; - if (net->users.entries() < 4 || nd.wires.empty() || (crit > 0.95)) - mode = 1; - - // This records the point where forwards and backwards routing met - int midpoint_wire = -1; - int explored = 1; - - for (; mode < 2; mode++) { - // Clear out the queues - if (!t.fwd_queue.empty()) { - std::priority_queue, QueuedWire::Greater> new_queue; - t.fwd_queue.swap(new_queue); - } - if (!t.bwd_queue.empty()) { - std::priority_queue, QueuedWire::Greater> new_queue; - t.bwd_queue.swap(new_queue); - } - // Unvisit any previously visited wires - reset_wires(t); - - ROUTE_LOG_DBG("src_wire = %s -> dst_wire = %s\n", ctx->nameOfWire(src_wire), ctx->nameOfWire(dst_wire)); - - // Add 'forward' direction startpoints to queue - auto seed_queue_fwd = [&](WireId wire, float wire_cost = 0) { - WireScore base_score; - base_score.cost = wire_cost; - int wire_idx = wire_to_idx.at(wire); - base_score.togo_cost = get_togo_cost(net, i, wire_idx, dst_wire, false, crit_weight); - t.fwd_queue.push(QueuedWire(wire_idx, base_score)); - set_visited_fwd(t, wire_idx, PipId()); - }; - auto &dst_data = flat_wires.at(dst_wire_idx); - // Look for nearby existing routing - for (int dy = -cfg.bb_margin_y; dy <= cfg.bb_margin_y; dy++) - for (int dx = -cfg.bb_margin_x; dx <= cfg.bb_margin_x; dx++) { - auto fnd = t.wire_by_loc.find(std::make_pair(dst_data.x + dx, dst_data.y + dy)); - if (fnd == t.wire_by_loc.end()) - continue; - for (WireId wire : fnd->second) { - ROUTE_LOG_DBG(" seeding with %s\n", ctx->nameOfWire(wire)); - seed_queue_fwd(wire); - } - } - - if (mode == 0 && t.fwd_queue.size() < 4) - continue; - - if (mode == 1 && !is_pseudo_const_net(net)) { - // Seed forwards with the source wire, if less than 8 existing wires added - seed_queue_fwd(src_wire); - } else { - set_visited_fwd(t, src_wire_idx, PipId()); - } - auto seed_queue_bwd = [&](WireId wire) { - WireScore base_score; - base_score.cost = 0; - int wire_idx = wire_to_idx.at(wire); - base_score.togo_cost = get_togo_cost(net, i, wire_idx, src_wire, true, crit_weight); - t.bwd_queue.push(QueuedWire(wire_idx, base_score)); - set_visited_bwd(t, wire_idx, PipId()); - }; - - // Seed backwards with the dest wire - seed_queue_bwd(dst_wire); - - int toexplore = 25000 * std::max(1, (ad.bb.x1 - ad.bb.x0) + (ad.bb.y1 - ad.bb.y0)); - int iter = 0; - - // Mode 0 required both queues to be live - while (((mode == 0) ? (!t.fwd_queue.empty() && !t.bwd_queue.empty()) - : (!t.fwd_queue.empty() || !t.bwd_queue.empty())) && - (!is_bb || iter < toexplore)) { - ++iter; - if (!t.fwd_queue.empty()) { - // Explore forwards - auto curr = t.fwd_queue.top(); - t.fwd_queue.pop(); - ++explored; - if (was_visited_bwd(curr.wire)) { - // Meet in the middle; done - midpoint_wire = curr.wire; - break; - } - auto &curr_data = flat_wires.at(curr.wire); - for (PipId dh : ctx->getPipsDownhill(curr_data.w)) { - // Skip pips outside of box in bounding-box mode - if (is_bb && !hit_test_pip(nd.bb, ctx->getPipLocation(dh))) - continue; - if (!ctx->checkPipAvailForNet(dh, net)) - continue; - WireId next = ctx->getPipDstWire(dh); - int next_idx = wire_to_idx.at(next); - if (was_visited_fwd(next_idx)) { - // Don't expand the same node twice. - continue; - } - auto &nwd = flat_wires.at(next_idx); - if (nwd.unavailable) - continue; - // Reserved for another net - if (nwd.reserved_net != -1 && nwd.reserved_net != net->udata) - continue; - // Don't allow the same wire to be bound to the same net with a different driving pip - auto fnd_wire = nd.wires.find(next); - if (fnd_wire != nd.wires.end() && fnd_wire->second.first != dh) - continue; - if (!thread_test_wire(t, nwd)) - continue; // thread safety issue - WireScore next_score; - next_score.cost = curr.score.cost + score_wire_for_arc(net, i, phys_pin, next, dh, crit_weight); - next_score.togo_cost = - cfg.estimate_weight * get_togo_cost(net, i, next_idx, dst_wire, false, crit_weight); - set_visited_fwd(t, next_idx, dh); - t.fwd_queue.push(QueuedWire(next_idx, next_score, t.rng.rng())); - } - } - if (!t.bwd_queue.empty()) { - // Explore backwards - auto curr = t.bwd_queue.top(); - t.bwd_queue.pop(); - ++explored; - if (was_visited_fwd(curr.wire)) { - // Meet in the middle; done - midpoint_wire = curr.wire; - break; - } - auto &curr_data = flat_wires.at(curr.wire); - // Don't allow the same wire to be bound to the same net with a different driving pip - PipId bound_pip; - auto fnd_wire = nd.wires.find(curr_data.w); - if (fnd_wire != nd.wires.end()) - bound_pip = fnd_wire->second.first; - - for (PipId uh : ctx->getPipsUphill(curr_data.w)) { - if (bound_pip != PipId() && bound_pip != uh) - continue; - if (is_bb && !hit_test_pip(nd.bb, ctx->getPipLocation(uh))) - continue; - if (!ctx->checkPipAvailForNet(uh, net)) - continue; - WireId next = ctx->getPipSrcWire(uh); - int next_idx = wire_to_idx.at(next); - if (was_visited_bwd(next_idx)) { - // Don't expand the same node twice. - continue; - } - auto &nwd = flat_wires.at(next_idx); - if (nwd.unavailable) - continue; - // Reserved for another net - if (nwd.reserved_net != -1 && nwd.reserved_net != net->udata) - continue; - if (!thread_test_wire(t, nwd)) - continue; // thread safety issue - WireScore next_score; - next_score.cost = curr.score.cost + score_wire_for_arc(net, i, phys_pin, next, uh, crit_weight); - next_score.togo_cost = - cfg.estimate_weight * get_togo_cost(net, i, next_idx, src_wire, true, crit_weight); - set_visited_bwd(t, next_idx, uh); - t.bwd_queue.push(QueuedWire(next_idx, next_score, t.rng.rng())); - } - } - } - if (midpoint_wire != -1) - break; - } - ArcRouteResult result = ARC_SUCCESS; - if (midpoint_wire != -1) { - ROUTE_LOG_DBG(" Routed (explored %d wires): ", explored); - int cursor_bwd = midpoint_wire; - while (was_visited_fwd(cursor_bwd)) { - PipId pip = flat_wires.at(cursor_bwd).pip_fwd; - if (pip == PipId() && cursor_bwd != src_wire_idx) - break; - bind_pip_internal(nd, i, cursor_bwd, pip); - if (ctx->debug && !is_mt) { - auto &wd = flat_wires.at(cursor_bwd); - ROUTE_LOG_DBG(" fwd wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w), - wd.curr_cong - 1, wd.hist_cong_cost, nd.wires.at(wd.w).second); - } - if (pip == PipId()) { - break; - } - ROUTE_LOG_DBG(" fwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x, - ctx->getPipLocation(pip).y); - cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip)); - } - - while (cursor_bwd != src_wire_idx) { - // Tack onto existing routing - WireId bwd_w = flat_wires.at(cursor_bwd).w; - if (!nd.wires.count(bwd_w)) - break; - auto &bound = nd.wires.at(bwd_w); - PipId pip = bound.first; - if (ctx->debug && !is_mt) { - auto &wd = flat_wires.at(cursor_bwd); - ROUTE_LOG_DBG(" ext wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w), - wd.curr_cong - 1, wd.hist_cong_cost, bound.second); - } - bind_pip_internal(nd, i, cursor_bwd, pip); - if (pip == PipId()) - break; - cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip)); - } - - NPNR_ASSERT(cursor_bwd == src_wire_idx); - - int cursor_fwd = midpoint_wire; - while (was_visited_bwd(cursor_fwd)) { - PipId pip = flat_wires.at(cursor_fwd).pip_bwd; - if (pip == PipId()) { - break; - } - ROUTE_LOG_DBG(" bwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x, - ctx->getPipLocation(pip).y); - cursor_fwd = wire_to_idx.at(ctx->getPipDstWire(pip)); - bind_pip_internal(nd, i, cursor_fwd, pip); - if (ctx->debug && !is_mt) { - auto &wd = flat_wires.at(cursor_fwd); - ROUTE_LOG_DBG(" bwd wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w), - wd.curr_cong - 1, wd.hist_cong_cost, nd.wires.at(wd.w).second); - } - } - NPNR_ASSERT(cursor_fwd == dst_wire_idx); - - update_wire_by_loc(t, net, i, phys_pin, is_mt); - t.processed_sinks.insert(dst_wire); - ad.routed = true; - auto arc_end = std::chrono::high_resolution_clock::now(); - ROUTE_LOG_DBG("Routing arc %d of net '%s' (is_bb = %d) took %02fs\n", i.idx(), ctx->nameOf(net), is_bb, - std::chrono::duration(arc_end - arc_start).count()); - } else { - auto arc_end = std::chrono::high_resolution_clock::now(); - ROUTE_LOG_DBG("Failed routing arc %d of net '%s' (is_bb = %d) took %02fs\n", i.idx(), ctx->nameOf(net), - is_bb, std::chrono::duration(arc_end - arc_start).count()); - result = ARC_RETRY_WITHOUT_BB; - } - reset_wires(t); - return result; - } -#undef ARC_ERR - - bool route_net(ThreadContext &t, NetInfo *net, bool is_mt) - { - -#ifdef ARCH_ECP5 - if (net->is_global) - return true; -#endif - - ROUTE_LOG_DBG("Routing net '%s'...\n", ctx->nameOf(net)); - - auto rstart = std::chrono::high_resolution_clock::now(); - - // Nothing to do if net is undriven - if (net->driver.cell == nullptr) - return true; - - bool have_failures = false; - t.processed_sinks.clear(); - t.route_arcs.clear(); - t.wire_by_loc.clear(); - t.in_wire_by_loc.clear(); - auto &nd = nets.at(net->udata); - bool failed_slack = false; - for (auto usr : net->users.enumerate()) - failed_slack |= arc_failed_slack(net, usr.index); - for (auto usr : net->users.enumerate()) { - auto &ad = nd.arcs.at(usr.index.idx()); - for (size_t j = 0; j < ad.size(); j++) { - // Ripup failed arcs to start with - // Check if arc is already legally routed - if (!failed_slack && check_arc_routing(net, usr.index, j)) { - update_wire_by_loc(t, net, usr.index, j, true); - continue; - } - - // Ripup arc to start with - ripup_arc(net, usr.index, j); - t.route_arcs.emplace_back(usr.index, j); - } - } - // Route most critical arc first - std::stable_sort(t.route_arcs.begin(), t.route_arcs.end(), - [&](std::pair, size_t> a, std::pair, size_t> b) { - return get_arc_crit(net, a.first) > get_arc_crit(net, b.first); - }); - for (auto a : t.route_arcs) { - auto res1 = route_arc(t, net, a.first, a.second, is_mt, true); - if (res1 == ARC_FATAL) - return false; // Arc failed irrecoverably - else if (res1 == ARC_RETRY_WITHOUT_BB) { - if (is_mt) { - // Can't break out of bounding box in multi-threaded mode, so mark this arc as a failure - have_failures = true; - } else { - // Attempt a re-route without the bounding box constraint - ROUTE_LOG_DBG("Rerouting arc %d.%d of net '%s' without bounding box, possible tricky routing...\n", - a.first.idx(), int(a.second), ctx->nameOf(net)); - auto res2 = route_arc(t, net, a.first, a.second, is_mt, false); - // If this also fails, no choice but to give up - if (res2 != ARC_SUCCESS) { - if (ctx->debug) { - log_info("Pre-bound routing: \n"); - for (auto &wire_pair : net->wires) { - log(" %s", ctx->nameOfWire(wire_pair.first)); - if (wire_pair.second.pip != PipId()) - log(" %s", ctx->nameOfPip(wire_pair.second.pip)); - log("\n"); - } - } - log_error("Failed to route arc %d.%d of net '%s', from %s to %s.\n", a.first.idx(), - int(a.second), ctx->nameOf(net), ctx->nameOfWire(ctx->getNetinfoSourceWire(net)), - ctx->nameOfWire(ctx->getNetinfoSinkWire(net, net->users.at(a.first), a.second))); - } - } - } - } - if (cfg.perf_profile) { - auto rend = std::chrono::high_resolution_clock::now(); - nets.at(net->udata).total_route_us += - (std::chrono::duration_cast(rend - rstart).count()); - } - return !have_failures; - } -#undef ROUTE_LOG_DBG - - int total_wire_use = 0; - int overused_wires = 0; - int total_overuse = 0; - std::vector route_queue; - std::set failed_nets; - - void update_congestion() - { - total_overuse = 0; - overused_wires = 0; - total_wire_use = 0; - failed_nets.clear(); - pool already_updated; - for (size_t i = 0; i < nets.size(); i++) { - auto &nd = nets.at(i); - for (const auto &w : nd.wires) { - ++total_wire_use; - auto &wd = wire_data(w.first); - if (wd.curr_cong > 1) { - if (already_updated.count(w.first)) { - ++total_overuse; - } else { - if (curr_cong_weight > 0) - wd.hist_cong_cost = - std::min(1e9, wd.hist_cong_cost + (wd.curr_cong - 1) * hist_cong_weight); - already_updated.insert(w.first); - ++overused_wires; - } - failed_nets.insert(i); - } - } - } - for (int n : failed_nets) { - auto &net_data = nets.at(n); - ++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 - } - } - } - - bool bind_and_check(NetInfo *net, store_index usr_idx, int phys_pin) - { -#ifdef ARCH_ECP5 - if (net->is_global) - return true; -#endif - bool success = true; - auto &nd = nets.at(net->udata); - auto &ad = nd.arcs.at(usr_idx.idx()).at(phys_pin); - auto &usr = net->users.at(usr_idx); - WireId src = ctx->getNetinfoSourceWire(net); - // Skip routes with no source - if (src == WireId()) - return true; - WireId dst = ctx->getNetinfoSinkWire(net, usr, phys_pin); - if (dst == WireId()) - return true; - - // Skip routes where there is no routing (special cases) - if (!ad.routed) { - if ((src == dst) && ctx->getBoundWireNet(dst) != net) - ctx->bindWire(src, net, STRENGTH_WEAK); - if (ctx->debug) { - log("Net %s not routed, not binding\n", ctx->nameOf(net)); - } - return true; - } - - WireId cursor = dst; - - std::vector to_bind; - - while (cursor != src) { - if (!ctx->checkWireAvail(cursor)) { - NetInfo *bound_net = ctx->getBoundWireNet(cursor); - if (bound_net != net) { - if (ctx->verbose) { - if (bound_net != nullptr) { - log_info("Failed to bind wire %s to net %s, bound to net %s\n", ctx->nameOfWire(cursor), - net->name.c_str(ctx), bound_net->name.c_str(ctx)); - } else { - log_info("Failed to bind wire %s to net %s, bound net nullptr\n", ctx->nameOfWire(cursor), - net->name.c_str(ctx)); - } - } - success = false; - break; - } - } - if (!nd.wires.count(cursor)) { - log("Failure details:\n"); - log(" Cursor: %s\n", ctx->nameOfWire(cursor)); - log_error("Internal error; incomplete route tree for arc %d of net %s.\n", usr_idx.idx(), - ctx->nameOf(net)); - } - PipId p = nd.wires.at(cursor).first; - if (ctx->checkPipAvailForNet(p, net)) { - NetInfo *bound_net = ctx->getBoundPipNet(p); - if (bound_net == nullptr) { - to_bind.push_back(p); - } - } else { - if (ctx->verbose) { - log_info("Failed to bind pip %s to net %s\n", ctx->nameOfPip(p), net->name.c_str(ctx)); - } - success = false; - break; - } - cursor = ctx->getPipSrcWire(p); - } - - if (success) { - if (ctx->getBoundWireNet(src) == nullptr) - ctx->bindWire(src, net, STRENGTH_WEAK); - for (auto tb : to_bind) - ctx->bindPip(tb, net, STRENGTH_WEAK); - } else { - ripup_arc(net, usr_idx, phys_pin); - failed_nets.insert(net->udata); - } - return success; - } - - int arch_fail = 0; - bool bind_and_check_all() - { - // Make sure arch is internally consistent before we mess with it. - ctx->check(); - - bool success = true; - std::vector net_wires; - for (auto net : nets_by_udata) { -#ifdef ARCH_ECP5 - if (net->is_global) - continue; -#endif - // Ripup wires and pips used by the net in nextpnr's structures - net_wires.clear(); - for (auto &w : net->wires) { - if (w.second.strength <= STRENGTH_STRONG) { - net_wires.push_back(w.first); - } else if (ctx->debug) { - log("Net %s didn't rip up wire %s because strength was %d\n", ctx->nameOf(net), - ctx->nameOfWire(w.first), w.second.strength); - } - } - for (auto w : net_wires) - ctx->unbindWire(w); - - if (ctx->debug) { - log("Ripped up %zu wires on net %s\n", net_wires.size(), ctx->nameOf(net)); - } - - // Bind the arcs using the routes we have discovered - for (auto usr : net->users.enumerate()) { - for (size_t phys_pin = 0; phys_pin < nets.at(net->udata).arcs.at(usr.index.idx()).size(); phys_pin++) { - if (!bind_and_check(net, usr.index, phys_pin)) { - ++arch_fail; - success = false; - } - } - } - } - - // Check that the arch is still internally consistent! - ctx->check(); - - return success; - } - - void write_wiretype_heatmap(std::ostream &out) - { - dict> cong_by_type; - size_t max_cong = 0; - // Build histogram - for (auto &wd : flat_wires) { - size_t val = wd.curr_cong; - IdString type = ctx->getWireType(wd.w); - max_cong = std::max(max_cong, val); - if (cong_by_type[type].size() <= max_cong) - cong_by_type[type].resize(max_cong + 1); - cong_by_type[type].at(val) += 1; - } - // Write csv - out << "type,"; - for (size_t i = 0; i <= max_cong; i++) - out << "bound=" << i << ","; - out << std::endl; - for (auto &ty : cong_by_type) { - out << ctx->nameOf(ty.first) << ","; - for (int count : ty.second) - out << count << ","; - out << std::endl; - } - } - - int mid_x = 0, mid_y = 0; - - void partition_nets() - { - // Create a histogram of positions in X and Y positions - std::map cxs, cys; - for (auto &n : nets) { - if (n.cx != -1) - ++cxs[n.cx]; - if (n.cy != -1) - ++cys[n.cy]; - } - // 4-way split for now - int accum_x = 0, accum_y = 0; - int halfway = int(nets.size()) / 2; - for (auto &p : cxs) { - if (accum_x < halfway && (accum_x + p.second) >= halfway) - mid_x = p.first; - accum_x += p.second; - } - for (auto &p : cys) { - if (accum_y < halfway && (accum_y + p.second) >= halfway) - mid_y = p.first; - accum_y += p.second; - } - if (ctx->verbose) { - log_info(" x splitpoint: %d\n", mid_x); - log_info(" y splitpoint: %d\n", mid_y); - } - std::vector bins(5, 0); - for (auto &n : nets) { - if (n.bb.x0 < mid_x && n.bb.x1 < mid_x && n.bb.y0 < mid_y && n.bb.y1 < mid_y) - ++bins[0]; // TL - else if (n.bb.x0 >= mid_x && n.bb.x1 >= mid_x && n.bb.y0 < mid_y && n.bb.y1 < mid_y) - ++bins[1]; // TR - else if (n.bb.x0 < mid_x && n.bb.x1 < mid_x && n.bb.y0 >= mid_y && n.bb.y1 >= mid_y) - ++bins[2]; // BL - else if (n.bb.x0 >= mid_x && n.bb.x1 >= mid_x && n.bb.y0 >= mid_y && n.bb.y1 >= mid_y) - ++bins[3]; // BR - else - ++bins[4]; // cross-boundary - } - if (ctx->verbose) - for (int i = 0; i < 5; i++) - log_info(" bin %d N=%d\n", i, bins[i]); - } - - void router_thread(ThreadContext &t, bool is_mt) - { - for (auto n : t.route_nets) { - bool result = route_net(t, n, is_mt); - if (!result) - t.failed_nets.push_back(n); - } - } - - void do_route() - { - // Don't multithread if fewer than 200 nets (heuristic) - if (route_queue.size() < 200) { - ThreadContext st; - st.rng.rngseed(ctx->rng64()); - st.bb = ArcBounds(0, 0, std::numeric_limits::max(), std::numeric_limits::max()); - for (size_t j = 0; j < route_queue.size(); j++) { - route_net(st, nets_by_udata[route_queue[j]], false); - } - return; - } - const int Nq = 4, Nv = 2, Nh = 2; - const int N = Nq + Nv + Nh; - std::vector tcs(N + 1); - for (auto &th : tcs) { - th.rng.rngseed(ctx->rng64()); - } - int le_x = mid_x; - int rs_x = mid_x; - int le_y = mid_y; - int rs_y = mid_y; - // Set up thread bounding boxes - tcs.at(0).bb = ArcBounds(0, 0, mid_x, mid_y); - tcs.at(1).bb = ArcBounds(mid_x + 1, 0, std::numeric_limits::max(), le_y); - tcs.at(2).bb = ArcBounds(0, mid_y + 1, mid_x, std::numeric_limits::max()); - tcs.at(3).bb = - ArcBounds(mid_x + 1, mid_y + 1, std::numeric_limits::max(), std::numeric_limits::max()); - - tcs.at(4).bb = ArcBounds(0, 0, std::numeric_limits::max(), mid_y); - tcs.at(5).bb = ArcBounds(0, mid_y + 1, std::numeric_limits::max(), std::numeric_limits::max()); - - tcs.at(6).bb = ArcBounds(0, 0, mid_x, std::numeric_limits::max()); - tcs.at(7).bb = ArcBounds(mid_x + 1, 0, std::numeric_limits::max(), std::numeric_limits::max()); - - tcs.at(8).bb = ArcBounds(0, 0, std::numeric_limits::max(), std::numeric_limits::max()); - - for (auto n : route_queue) { - auto &nd = nets.at(n); - auto ni = nets_by_udata.at(n); - int bin = N; - // Quadrants - if (nd.bb.x0 < le_x && nd.bb.x1 < le_x && nd.bb.y0 < le_y && nd.bb.y1 < le_y) - bin = 0; - else if (nd.bb.x0 >= rs_x && nd.bb.x1 >= rs_x && nd.bb.y0 < le_y && nd.bb.y1 < le_y) - bin = 1; - else if (nd.bb.x0 < le_x && nd.bb.x1 < le_x && nd.bb.y0 >= rs_y && nd.bb.y1 >= rs_y) - bin = 2; - else if (nd.bb.x0 >= rs_x && nd.bb.x1 >= rs_x && nd.bb.y0 >= rs_y && nd.bb.y1 >= rs_y) - bin = 3; - // Vertical split - else if (nd.bb.y0 < le_y && nd.bb.y1 < le_y) - bin = Nq + 0; - else if (nd.bb.y0 >= rs_y && nd.bb.y1 >= rs_y) - bin = Nq + 1; - // Horizontal split - else if (nd.bb.x0 < le_x && nd.bb.x1 < le_x) - bin = Nq + Nv + 0; - else if (nd.bb.x0 >= rs_x && nd.bb.x1 >= rs_x) - bin = Nq + Nv + 1; - tcs.at(bin).route_nets.push_back(ni); - } - if (ctx->verbose) - log_info("%d/%d nets not multi-threadable\n", int(tcs.at(N).route_nets.size()), int(route_queue.size())); -#ifdef NPNR_DISABLE_THREADS - // Singlethreaded routing - quadrants - for (int i = 0; i < Nq; i++) { - router_thread(tcs.at(i), /*is_mt=*/false); - } - // Vertical splits - for (int i = Nq; i < Nq + Nv; i++) { - router_thread(tcs.at(i), /*is_mt=*/false); - } - // Horizontal splits - for (int i = Nq + Nv; i < Nq + Nv + Nh; i++) { - router_thread(tcs.at(i), /*is_mt=*/false); - } -#else - // Multithreaded part of routing - quadrants - std::vector threads; - for (int i = 0; i < Nq; i++) { - threads.emplace_back([this, &tcs, i]() { router_thread(tcs.at(i), /*is_mt=*/true); }); - } - for (auto &t : threads) - t.join(); - threads.clear(); - // Vertical splits - for (int i = Nq; i < Nq + Nv; i++) { - threads.emplace_back([this, &tcs, i]() { router_thread(tcs.at(i), /*is_mt=*/true); }); - } - for (auto &t : threads) - t.join(); - threads.clear(); - // Horizontal splits - for (int i = Nq + Nv; i < Nq + Nv + Nh; i++) { - threads.emplace_back([this, &tcs, i]() { router_thread(tcs.at(i), /*is_mt=*/true); }); - } - for (auto &t : threads) - t.join(); - threads.clear(); -#endif - // Singlethreaded part of routing - nets that cross partitions - // or don't fit within bounding box - for (auto st_net : tcs.at(N).route_nets) - route_net(tcs.at(N), st_net, false); - // Failed nets - for (int i = 0; i < N; i++) - for (auto fail : tcs.at(i).failed_nets) - route_net(tcs.at(N), fail, false); - } - - delay_t get_route_delay(int net, store_index usr_idx, int phys_idx) - { - auto &nd = nets.at(net); - auto &ad = nd.arcs.at(usr_idx.idx()).at(phys_idx); - WireId cursor = ad.sink_wire; - if (cursor == WireId() || nd.src_wire == WireId()) - return 0; - delay_t delay = 0; - while (true) { - delay += ctx->getWireDelay(cursor).maxDelay(); - if (!nd.wires.count(cursor)) - break; - auto &bound = nd.wires.at(cursor); - if (bound.first == PipId()) - break; - delay += ctx->getPipDelay(bound.first).maxDelay(); - cursor = ctx->getPipSrcWire(bound.first); - } - NPNR_ASSERT(cursor == nd.src_wire); - return delay; - } - - void update_route_delays() - { - for (int net : route_queue) { - NetInfo *ni = nets_by_udata.at(net); -#ifdef ARCH_ECP5 - if (ni->is_global) - continue; -#endif - auto &nd = nets.at(net); - for (auto usr : ni->users.enumerate()) { - delay_t arc_delay = 0; - for (int j = 0; j < int(nd.arcs.at(usr.index.idx()).size()); j++) - arc_delay = std::max(arc_delay, get_route_delay(net, usr.index, j)); - tmg.set_route_delay(CellPortKey(usr.value), DelayPair(arc_delay)); - } - } - } - - void operator()() - { - log_info("Running router2...\n"); - log_info("Setting up routing resources...\n"); - auto rstart = std::chrono::high_resolution_clock::now(); - setup_nets(); - setup_wires(); - find_all_reserved_wires(); - partition_nets(); - curr_cong_weight = cfg.init_curr_cong_weight; - hist_cong_weight = cfg.hist_cong_weight; - ThreadContext st; - int iter = 1; - - ScopeLock lock(ctx); - - for (size_t i = 0; i < nets_by_udata.size(); i++) - route_queue.push_back(i); - - timing_driven = ctx->setting("timing_driven"); - if (ctx->settings.count(ctx->id("router/tmg_ripup"))) - timing_driven_ripup = timing_driven && ctx->setting("router/tmg_ripup"); - else - timing_driven_ripup = false; - log_info("Running main router loop...\n"); - if (timing_driven) - tmg.run(true); - do { - ctx->sorted_shuffle(route_queue); - - if (timing_driven && int(route_queue.size()) >= 30) { - for (auto n : route_queue) { - NetInfo *ni = nets_by_udata.at(n); - auto &net = nets.at(n); - net.max_crit = 0; - for (auto &usr : ni->users) { - float c = tmg.get_criticality(CellPortKey(usr)); - net.max_crit = std::max(net.max_crit, c); - } - } - std::stable_sort(route_queue.begin(), route_queue.end(), - [&](int na, int nb) { return nets.at(na).max_crit > nets.at(nb).max_crit; }); - } - - do_route(); - update_route_delays(); - route_queue.clear(); - update_congestion(); - - if (!cfg.heatmap.empty()) { - std::string filename(cfg.heatmap + "_" + std::to_string(iter) + ".csv"); - std::ofstream cong_map(filename); - if (!cong_map) - log_error("Failed to open wiretype heatmap %s for writing.\n", filename.c_str()); - write_wiretype_heatmap(cong_map); - log_info(" wrote wiretype heatmap to %s.\n", filename.c_str()); - } - int tmgfail = 0; - if (timing_driven) - tmg.run(false); - if (timing_driven_ripup && iter < 500) { - for (size_t i = 0; i < nets_by_udata.size(); i++) { - NetInfo *ni = nets_by_udata.at(i); - for (auto usr : ni->users.enumerate()) { - if (arc_failed_slack(ni, usr.index)) { - failed_nets.insert(i); - ++tmgfail; - } - } - } - } - if (overused_wires == 0 && tmgfail == 0) { - // Try and actually bind nextpnr Arch API wires - bind_and_check_all(); - } - for (auto cn : failed_nets) - route_queue.push_back(cn); - if (timing_driven_ripup) - log_info(" iter=%d wires=%d overused=%d overuse=%d tmgfail=%d archfail=%s\n", iter, total_wire_use, - overused_wires, total_overuse, tmgfail, - (overused_wires > 0 || tmgfail > 0) ? "NA" : std::to_string(arch_fail).c_str()); - else - log_info(" iter=%d wires=%d overused=%d overuse=%d archfail=%s\n", iter, total_wire_use, - overused_wires, total_overuse, - (overused_wires > 0 || tmgfail > 0) ? "NA" : std::to_string(arch_fail).c_str()); - ++iter; - if (curr_cong_weight < 1e9) - curr_cong_weight += cfg.curr_cong_mult; - } while (!failed_nets.empty()); - if (cfg.perf_profile) { - std::vector> nets_by_runtime; - for (auto &n : nets_by_udata) { - nets_by_runtime.emplace_back(nets.at(n->udata).total_route_us, n->name); - } - std::sort(nets_by_runtime.begin(), nets_by_runtime.end(), std::greater>()); - log_info("1000 slowest nets by runtime:\n"); - for (int i = 0; i < std::min(int(nets_by_runtime.size()), 1000); i++) { - log(" %80s %6d %.1fms\n", nets_by_runtime.at(i).second.c_str(ctx), - int(ctx->nets.at(nets_by_runtime.at(i).second)->users.entries()), - nets_by_runtime.at(i).first / 1000.0); - } - } - auto rend = std::chrono::high_resolution_clock::now(); - log_info("Router2 time %.02fs\n", std::chrono::duration(rend - rstart).count()); - - log_info("Running router1 to check that route is legal...\n"); - - lock.unlock_early(); - - router1(ctx, Router1Cfg(ctx)); - } -}; -} // namespace - -void router2(Context *ctx, const Router2Cfg &cfg) -{ - Router2 rt(ctx, cfg); - rt.ctx = ctx; - rt(); -} - -Router2Cfg::Router2Cfg(Context *ctx) -{ - backwards_max_iter = ctx->setting("router2/bwdMaxIter", 20); - global_backwards_max_iter = ctx->setting("router2/glbBwdMaxIter", 200); - bb_margin_x = ctx->setting("router2/bbMargin/x", 3); - bb_margin_y = ctx->setting("router2/bbMargin/y", 3); - ipin_cost_adder = ctx->setting("router2/ipinCostAdder", 0.0f); - bias_cost_factor = ctx->setting("router2/biasCostFactor", 0.25f); - init_curr_cong_weight = ctx->setting("router2/initCurrCongWeight", 0.5f); - hist_cong_weight = ctx->setting("router2/histCongWeight", 1.0f); - curr_cong_mult = ctx->setting("router2/currCongWeightMult", 2.0f); - estimate_weight = ctx->setting("router2/estimateWeight", 1.25f); - perf_profile = ctx->setting("router2/perfProfile", false); - if (ctx->settings.count(ctx->id("router2/heatmap"))) - heatmap = ctx->settings.at(ctx->id("router2/heatmap")).as_string(); - else - heatmap = ""; -} - -NEXTPNR_NAMESPACE_END diff --git a/common/router2.h b/common/router2.h deleted file mode 100644 index 629453c6..00000000 --- a/common/router2.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2019 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 - -inline float default_base_cost(Context *ctx, WireId wire, PipId pip, float crit_weight) -{ - (void)crit_weight; // unused - return ctx->getDelayNS(ctx->getPipDelay(pip).maxDelay() + ctx->getWireDelay(wire).maxDelay() + - ctx->getDelayEpsilon()); -} - -struct Router2Cfg -{ - Router2Cfg(Context *ctx); - - // Maximum iterations for backwards routing attempt - int backwards_max_iter; - // Maximum iterations for backwards routing attempt for global nets - int global_backwards_max_iter; - // Padding added to bounding boxes to account for imperfect routing, - // congestion, etc - int bb_margin_x, bb_margin_y; - // Cost factor added to input pin wires; effectively reduces the - // benefit of sharing interconnect - float ipin_cost_adder; - // Cost factor for "bias" towards center location of net - float bias_cost_factor; - // Starting current and historical congestion cost factor - float init_curr_cong_weight, hist_cong_weight; - // Current congestion cost multiplier - float curr_cong_mult; - - // Weight given to delay estimate in A*. Higher values - // mean faster and more directed routing, at the risk - // of choosing a less congestion/delay-optimal route - float estimate_weight; - - // Print additional performance profiling information - bool perf_profile = false; - - std::string heatmap; - std::function get_base_cost = default_base_cost; -}; - -void router2(Context *ctx, const Router2Cfg &cfg); - -NEXTPNR_NAMESPACE_END diff --git a/common/scope_lock.h b/common/scope_lock.h deleted file mode 100644 index 2f0f767c..00000000 --- a/common/scope_lock.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2021 Symbiflow Authors. - * - * 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 SCOPE_LOCK_H -#define SCOPE_LOCK_H - -#include - -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -// Provides a simple RAII locking object. ScopeLock takes a lock when -// constructed, and releases the lock on destruction or if "unlock_early" is -// called. -// -// LockingObject must have a method "void lock(void)" and "void unlock(void)". -template class ScopeLock -{ - public: - ScopeLock(LockingObject *obj) : obj_(obj), locked_(false) - { - obj_->lock(); - locked_ = true; - } - ScopeLock(const ScopeLock &other) = delete; - ScopeLock(const ScopeLock &&other) = delete; - - ~ScopeLock() - { - if (locked_) { - obj_->unlock(); - } - } - void unlock_early() - { - if (!locked_) { - throw std::runtime_error("Lock already released?"); - } - locked_ = false; - obj_->unlock(); - } - - private: - LockingObject *obj_; - bool locked_; -}; - -NEXTPNR_NAMESPACE_END - -#endif /* SCOPE_LOCK_H */ diff --git a/common/sdf.cc b/common/sdf.cc deleted file mode 100644 index acff56ed..00000000 --- a/common/sdf.cc +++ /dev/null @@ -1,334 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2019 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" -#include "util.h" - -NEXTPNR_NAMESPACE_BEGIN - -namespace SDF { - -struct MinMaxTyp -{ - double min, typ, max; -}; - -struct RiseFallDelay -{ - MinMaxTyp rise, fall; -}; - -struct PortAndEdge -{ - std::string port; - ClockEdge edge; -}; - -struct IOPath -{ - std::string from, to; - RiseFallDelay delay; -}; - -struct TimingCheck -{ - enum CheckType - { - SETUPHOLD, - PERIOD, - WIDTH - } type; - PortAndEdge from, to; - RiseFallDelay delay; -}; - -struct Cell -{ - std::string celltype, instance; - std::vector iopaths; - std::vector checks; -}; - -struct CellPort -{ - std::string cell, port; -}; - -struct Interconnect -{ - CellPort from, to; - RiseFallDelay delay; -}; - -struct SDFWriter -{ - bool cvc_mode = false; - std::vector cells; - std::vector conn; - std::string sdfversion, design, vendor, program; - - std::string format_name(const std::string &name) - { - std::string fmt = "\""; - for (char c : name) { - if (c == '\\' || c == '\"') - fmt += "\""; - fmt += c; - } - fmt += "\""; - return fmt; - } - - std::string escape_name(const std::string &name) - { - std::string esc; - for (char c : name) { - if (c == '$' || c == '\\' || c == '[' || c == ']' || c == ':' || (cvc_mode && c == '.')) - esc += '\\'; - esc += c; - } - return esc; - } - - std::string timing_check_name(TimingCheck::CheckType type) - { - switch (type) { - case TimingCheck::SETUPHOLD: - return "SETUPHOLD"; - case TimingCheck::PERIOD: - return "PERIOD"; - case TimingCheck::WIDTH: - return "WIDTH"; - default: - NPNR_ASSERT_FALSE("unknown timing check type"); - } - } - - void write_delay(std::ostream &out, const RiseFallDelay &delay) - { - write_delay(out, delay.rise); - out << " "; - write_delay(out, delay.fall); - } - - void write_delay(std::ostream &out, const MinMaxTyp &delay) - { - if (cvc_mode) - out << "(" << int(delay.min) << ":" << int(delay.typ) << ":" << int(delay.max) << ")"; - else - out << "(" << delay.min << ":" << delay.typ << ":" << delay.max << ")"; - } - - void write_port(std::ostream &out, const CellPort &port) - { - if (cvc_mode) - out << escape_name(port.cell) + "." + escape_name(port.port); - else - out << escape_name(port.cell + "/" + port.port); - } - - void write_portedge(std::ostream &out, const PortAndEdge &pe) - { - out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << escape_name(pe.port) << ")"; - } - - void write(std::ostream &out) - { - out << "(DELAYFILE" << std::endl; - // Headers and metadata - out << " (SDFVERSION " << format_name(sdfversion) << ")" << std::endl; - out << " (DESIGN " << format_name(design) << ")" << std::endl; - out << " (VENDOR " << format_name(vendor) << ")" << std::endl; - out << " (PROGRAM " << format_name(program) << ")" << std::endl; - out << " (DIVIDER " << (cvc_mode ? "." : "/") << ")" << std::endl; - out << " (TIMESCALE 1ps)" << std::endl; - // Write interconnect delays, with the main design begin a "cell" - out << " (CELL" << std::endl; - out << " (CELLTYPE " << format_name(design) << ")" << std::endl; - out << " (INSTANCE )" << std::endl; - out << " (DELAY" << std::endl; - out << " (ABSOLUTE" << std::endl; - for (auto &ic : conn) { - out << " (INTERCONNECT "; - write_port(out, ic.from); - out << " "; - write_port(out, ic.to); - out << " "; - write_delay(out, ic.delay); - out << ")" << std::endl; - } - out << " )" << std::endl; - out << " )" << std::endl; - out << " )" << std::endl; - // Write cells - for (auto &cell : cells) { - out << " (CELL" << std::endl; - out << " (CELLTYPE " << format_name(cell.celltype) << ")" << std::endl; - out << " (INSTANCE " << escape_name(cell.instance) << ")" << std::endl; - // IOPATHs (combinational delay and clock-to-q) - if (!cell.iopaths.empty()) { - out << " (DELAY" << std::endl; - out << " (ABSOLUTE" << std::endl; - for (auto &path : cell.iopaths) { - out << " (IOPATH " << escape_name(path.from) << " " << escape_name(path.to) << " "; - write_delay(out, path.delay); - out << ")" << std::endl; - } - out << " )" << std::endl; - out << " )" << std::endl; - } - // Timing Checks (setup/hold, period, width) - if (!cell.checks.empty()) { - out << " (TIMINGCHECK" << std::endl; - for (auto &check : cell.checks) { - out << " (" << timing_check_name(check.type) << " "; - write_portedge(out, check.from); - out << " "; - if (check.type == TimingCheck::SETUPHOLD) { - write_portedge(out, check.to); - out << " "; - } - if (check.type == TimingCheck::SETUPHOLD) - write_delay(out, check.delay); - else - write_delay(out, check.delay.rise); - out << ")" << std::endl; - } - out << " )" << std::endl; - } - out << " )" << std::endl; - } - out << ")" << std::endl; - } -}; - -} // namespace SDF - -void Context::writeSDF(std::ostream &out, bool cvc_mode) const -{ - using namespace SDF; - SDFWriter wr; - wr.cvc_mode = cvc_mode; - wr.design = str_or_default(attrs, id("module"), "top"); - wr.sdfversion = "3.0"; - wr.vendor = "nextpnr"; - wr.program = "nextpnr"; - - const double delay_scale = 1000; - // Convert from DelayQuad to SDF-friendly RiseFallDelay - auto convert_delay = [&](const DelayQuad &dly) { - RiseFallDelay rf; - rf.rise.min = getDelayNS(dly.minRiseDelay()) * delay_scale; - rf.rise.typ = getDelayNS((dly.minRiseDelay() + dly.maxRiseDelay()) / 2) * delay_scale; // fixme: typ delays? - rf.rise.max = getDelayNS(dly.maxRiseDelay()) * delay_scale; - rf.fall.min = getDelayNS(dly.minFallDelay()) * delay_scale; - rf.fall.typ = getDelayNS((dly.minFallDelay() + dly.maxFallDelay()) / 2) * delay_scale; // fixme: typ delays? - rf.fall.max = getDelayNS(dly.maxFallDelay()) * delay_scale; - return rf; - }; - - auto convert_setuphold = [&](const DelayPair &setup, const DelayPair &hold) { - RiseFallDelay rf; - rf.rise.min = getDelayNS(setup.minDelay()) * delay_scale; - rf.rise.typ = getDelayNS((setup.minDelay() + setup.maxDelay()) / 2) * delay_scale; // fixme: typ delays? - rf.rise.max = getDelayNS(setup.maxDelay()) * delay_scale; - rf.fall.min = getDelayNS(hold.minDelay()) * delay_scale; - rf.fall.typ = getDelayNS((hold.minDelay() + hold.maxDelay()) / 2) * delay_scale; // fixme: typ delays? - rf.fall.max = getDelayNS(hold.maxDelay()) * delay_scale; - return rf; - }; - - for (const auto &cell : cells) { - Cell sc; - const CellInfo *ci = cell.second.get(); - sc.instance = ci->name.str(this); - sc.celltype = ci->type.str(this); - for (auto port : ci->ports) { - int clockCount = 0; - TimingPortClass cls = getPortTimingClass(ci, port.first, clockCount); - if (cls == TMG_IGNORE) - continue; - if (port.second.net == nullptr) - continue; // Ignore disconnected ports - if (port.second.type != PORT_IN) { - // Add combinational paths to this output (or inout) - for (auto other : ci->ports) { - if (other.second.net == nullptr) - continue; - if (other.second.type == PORT_OUT) - continue; - DelayQuad dly; - if (!getCellDelay(ci, other.first, port.first, dly)) - continue; - IOPath iop; - iop.from = other.first.str(this); - iop.to = port.first.str(this); - iop.delay = convert_delay(dly); - sc.iopaths.push_back(iop); - } - // Add clock-to-output delays, also as IOPaths - if (cls == TMG_REGISTER_OUTPUT) - for (int i = 0; i < clockCount; i++) { - auto clkInfo = getPortClockingInfo(ci, port.first, i); - IOPath cqp; - cqp.from = clkInfo.clock_port.str(this); - cqp.to = port.first.str(this); - cqp.delay = convert_delay(clkInfo.clockToQ); - sc.iopaths.push_back(cqp); - } - } - if (port.second.type != PORT_OUT && cls == TMG_REGISTER_INPUT) { - // Add setup/hold checks - for (int i = 0; i < clockCount; i++) { - auto clkInfo = getPortClockingInfo(ci, port.first, i); - TimingCheck chk; - chk.from.edge = RISING_EDGE; // Add setup/hold checks equally for rising and falling edges - chk.from.port = port.first.str(this); - chk.to.edge = clkInfo.edge; - chk.to.port = clkInfo.clock_port.str(this); - chk.type = TimingCheck::SETUPHOLD; - chk.delay = convert_setuphold(clkInfo.setup, clkInfo.hold); - sc.checks.push_back(chk); - chk.from.edge = FALLING_EDGE; - sc.checks.push_back(chk); - } - } - } - wr.cells.push_back(sc); - } - - for (auto &net : nets) { - NetInfo *ni = net.second.get(); - if (ni->driver.cell == nullptr) - continue; - for (auto &usr : ni->users) { - Interconnect ic; - ic.from.cell = ni->driver.cell->name.str(this); - ic.from.port = ni->driver.port.str(this); - ic.to.cell = usr.cell->name.str(this); - ic.to.port = usr.port.str(this); - // FIXME: min/max routing delay - ic.delay = convert_delay(DelayQuad(getNetinfoRouteDelay(ni, usr))); - wr.conn.push_back(ic); - } - } - wr.write(out); -} - -NEXTPNR_NAMESPACE_END diff --git a/common/sso_array.h b/common/sso_array.h deleted file mode 100644 index 80e7d1c1..00000000 --- a/common/sso_array.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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 SSO_ARRAY_H -#define SSO_ARRAY_H - -#include - -#include "nextpnr_assertions.h" -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -// An small size optimised array that is statically allocated when the size is N or less; heap allocated otherwise -template class SSOArray -{ - private: - union - { - T data_static[N]; - T *data_heap; - }; - std::size_t m_size; - inline bool is_heap() const { return (m_size > N); } - void alloc() - { - if (is_heap()) { - data_heap = new T[m_size]; - } - } - - public: - T *data() { return is_heap() ? data_heap : data_static; } - const T *data() const { return is_heap() ? data_heap : data_static; } - std::size_t size() const { return m_size; } - - T *begin() { return data(); } - T *end() { return data() + m_size; } - const T *begin() const { return data(); } - const T *end() const { return data() + m_size; } - - SSOArray() : m_size(0){}; - - SSOArray(std::size_t size, const T &init = T()) : m_size(size) - { - alloc(); - std::fill(begin(), end(), init); - } - - SSOArray(const SSOArray &other) : m_size(other.size()) - { - alloc(); - std::copy(other.begin(), other.end(), begin()); - } - - SSOArray(SSOArray &&other) : m_size(other.size()) - { - if (is_heap()) - data_heap = other.data_heap; - else - std::copy(other.begin(), other.end(), begin()); - other.m_size = 0; - } - SSOArray &operator=(const SSOArray &other) - { - if (&other == this) - return *this; - if (is_heap()) - delete[] data_heap; - m_size = other.m_size; - alloc(); - std::copy(other.begin(), other.end(), begin()); - return *this; - } - - template SSOArray(const Tother &other) : m_size(other.size()) - { - alloc(); - std::copy(other.begin(), other.end(), begin()); - } - - ~SSOArray() - { - if (is_heap()) { - delete[] data_heap; - } - } - - bool operator==(const SSOArray &other) const - { - if (size() != other.size()) - return false; - return std::equal(begin(), end(), other.begin()); - } - bool operator!=(const SSOArray &other) const - { - if (size() != other.size()) - return true; - return !std::equal(begin(), end(), other.begin()); - } - T &operator[](std::size_t idx) - { - NPNR_ASSERT(idx < m_size); - return data()[idx]; - } - const T &operator[](std::size_t idx) const - { - NPNR_ASSERT(idx < m_size); - return data()[idx]; - } -}; - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/str_ring_buffer.cc b/common/str_ring_buffer.cc deleted file mode 100644 index 443d8612..00000000 --- a/common/str_ring_buffer.cc +++ /dev/null @@ -1,34 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * - * 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 "str_ring_buffer.h" - -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -std::string &StrRingBuffer::next() -{ - std::string &s = buffer.at(index++); - if (index >= N) - index = 0; - return s; -} - -NEXTPNR_NAMESPACE_END diff --git a/common/str_ring_buffer.h b/common/str_ring_buffer.h deleted file mode 100644 index 42583beb..00000000 --- a/common/str_ring_buffer.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Claire Xenia Wolf - * Copyright (C) 2018 Serge Bazanski - * - * 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 STR_RING_BUFFER_H -#define STR_RING_BUFFER_H - -#include -#include - -#include "nextpnr_namespaces.h" - -NEXTPNR_NAMESPACE_BEGIN - -// A ring buffer of strings, so we can return a simple const char * pointer for %s formatting - inspired by how logging -// in Yosys works Let's just hope noone tries to log more than 100 things in one call.... -class StrRingBuffer -{ - private: - static const size_t N = 100; - std::array buffer; - size_t index = 0; - - public: - std::string &next(); -}; - -NEXTPNR_NAMESPACE_END - -#endif /* STR_RING_BUFFER_H */ diff --git a/common/svg.cc b/common/svg.cc deleted file mode 100644 index c5e2ea36..00000000 --- a/common/svg.cc +++ /dev/null @@ -1,152 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2020 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 -#include -#include "log.h" -#include "nextpnr.h" -#include "util.h" - -NEXTPNR_NAMESPACE_BEGIN -namespace { -struct SVGWriter -{ - const Context *ctx; - std::ostream &out; - float scale = 500.0; - bool hide_inactive = false; - SVGWriter(const Context *ctx, std::ostream &out) : ctx(ctx), out(out){}; - const char *get_stroke_colour(GraphicElement::style_t style) - { - switch (style) { - case GraphicElement::STYLE_GRID: - return "#CCC"; - case GraphicElement::STYLE_FRAME: - return "#808080"; - case GraphicElement::STYLE_INACTIVE: - return "#C0C0C0"; - case GraphicElement::STYLE_ACTIVE: - return "#FF3030"; - default: - return "#000"; - } - } - - void write_decal(const DecalXY &dxy) - { - for (const auto &el : ctx->getDecalGraphics(dxy.decal)) { - if (el.style == GraphicElement::STYLE_HIDDEN || - (hide_inactive && el.style == GraphicElement::STYLE_INACTIVE)) - continue; - switch (el.type) { - case GraphicElement::TYPE_LINE: - case GraphicElement::TYPE_ARROW: - case GraphicElement::TYPE_LOCAL_LINE: - case GraphicElement::TYPE_LOCAL_ARROW: - out << stringf("", (el.x1 + dxy.x) * scale, - (el.y1 + dxy.y) * scale, (el.x2 + dxy.x) * scale, (el.y2 + dxy.y) * scale, - get_stroke_colour(el.style)) - << std::endl; - break; - case GraphicElement::TYPE_BOX: - out << stringf("", - (el.x1 + dxy.x) * scale, (el.y1 + dxy.y) * scale, (el.x2 - el.x1) * scale, - (el.y2 - el.y1) * scale, get_stroke_colour(el.style), - el.style == GraphicElement::STYLE_ACTIVE ? "#FF8080" : "none") - << std::endl; - break; - default: - break; - } - } - } - - void operator()(const std::string &flags) - { - std::vector options; - boost::algorithm::split(options, flags, boost::algorithm::is_space()); - bool noroute = false; - for (const auto &opt : options) { - if (boost::algorithm::starts_with(opt, "scale=")) { - scale = float(std::stod(opt.substr(6))); - continue; - } else if (opt == "hide_routing") { - noroute = true; - } else if (opt == "hide_inactive") { - hide_inactive = true; - } else { - log_error("Unknown SVG option '%s'\n", opt.c_str()); - } - } - float max_x = 0, max_y = 0; - for (auto group : ctx->getGroups()) { - auto decal = ctx->getGroupDecal(group); - for (auto el : ctx->getDecalGraphics(decal.decal)) { - max_x = std::max(max_x, decal.x + el.x1 + 1); - max_y = std::max(max_y, decal.y + el.y1 + 1); - } - } - for (auto bel : ctx->getBels()) { - auto decal = ctx->getBelDecal(bel); - for (auto el : ctx->getDecalGraphics(decal.decal)) { - max_x = std::max(max_x, decal.x + el.x1 + 1); - max_y = std::max(max_y, decal.y + el.y1 + 1); - } - } - for (auto wire : ctx->getWires()) { - auto decal = ctx->getWireDecal(wire); - for (auto el : ctx->getDecalGraphics(decal.decal)) { - max_x = std::max(max_x, decal.x + el.x1 + 1); - max_y = std::max(max_y, decal.y + el.y1 + 1); - } - } - for (auto pip : ctx->getPips()) { - auto decal = ctx->getPipDecal(pip); - for (auto el : ctx->getDecalGraphics(decal.decal)) { - max_x = std::max(max_x, decal.x + el.x1 + 1); - max_y = std::max(max_y, decal.y + el.y1 + 1); - } - } - out << "" << std::endl; - out << stringf("", - max_x * scale, max_y * scale, max_x * scale, max_y * scale) - << std::endl; - out << "" << std::endl; - for (auto group : ctx->getGroups()) - write_decal(ctx->getGroupDecal(group)); - for (auto bel : ctx->getBels()) - write_decal(ctx->getBelDecal(bel)); - if (!noroute) { - for (auto wire : ctx->getWires()) - write_decal(ctx->getWireDecal(wire)); - for (auto pip : ctx->getPips()) - write_decal(ctx->getPipDecal(pip)); - } - out << "" << std::endl; - } -}; -} // namespace - -void Context::writeSVG(const std::string &filename, const std::string &flags) const -{ - std::ofstream out(filename); - SVGWriter(this, out)(flags); -} - -NEXTPNR_NAMESPACE_END diff --git a/common/timing.cc b/common/timing.cc deleted file mode 100644 index 834785fb..00000000 --- a/common/timing.cc +++ /dev/null @@ -1,1515 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 gatecat - * Copyright (C) 2018 Eddie Hung - * - * 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 "timing.h" -#include -#include -#include -#include -#include -#include "log.h" -#include "util.h" - -NEXTPNR_NAMESPACE_BEGIN - -void TimingAnalyser::setup() -{ - init_ports(); - get_cell_delays(); - topo_sort(); - setup_port_domains(); - run(); -} - -void TimingAnalyser::run(bool update_route_delays) -{ - reset_times(); - if (update_route_delays) - get_route_delays(); - walk_forward(); - walk_backward(); - compute_slack(); - compute_criticality(); -} - -void TimingAnalyser::init_ports() -{ - // Per cell port structures - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - for (auto &port : ci->ports) { - auto &data = ports[CellPortKey(ci->name, port.first)]; - data.type = port.second.type; - data.cell_port = CellPortKey(ci->name, port.first); - } - } -} - -void TimingAnalyser::get_cell_delays() -{ - for (auto &port : ports) { - CellInfo *ci = cell_info(port.first); - auto &pi = port_info(port.first); - auto &pd = port.second; - - IdString name = port.first.port; - // Ignore dangling ports altogether for timing purposes - if (!pi.net) - continue; - pd.cell_arcs.clear(); - int clkInfoCount = 0; - TimingPortClass cls = ctx->getPortTimingClass(ci, name, clkInfoCount); - if (cls == TMG_STARTPOINT || cls == TMG_ENDPOINT || cls == TMG_CLOCK_INPUT || cls == TMG_GEN_CLOCK || - cls == TMG_IGNORE) - continue; - if (pi.type == PORT_IN) { - // Input ports might have setup/hold relationships - if (cls == TMG_REGISTER_INPUT) { - for (int i = 0; i < clkInfoCount; i++) { - auto info = ctx->getPortClockingInfo(ci, name, i); - if (!ci->ports.count(info.clock_port) || ci->ports.at(info.clock_port).net == nullptr) - continue; - pd.cell_arcs.emplace_back(CellArc::SETUP, info.clock_port, DelayQuad(info.setup, info.setup), - info.edge); - pd.cell_arcs.emplace_back(CellArc::HOLD, info.clock_port, DelayQuad(info.hold, info.hold), - info.edge); - } - } - // Combinational delays through cell - for (auto &other_port : ci->ports) { - auto &op = other_port.second; - // ignore dangling ports and non-outputs - if (op.net == nullptr || op.type != PORT_OUT) - continue; - DelayQuad delay; - bool is_path = ctx->getCellDelay(ci, name, other_port.first, delay); - if (is_path) - pd.cell_arcs.emplace_back(CellArc::COMBINATIONAL, other_port.first, delay); - } - } else if (pi.type == PORT_OUT) { - // Output ports might have clk-to-q relationships - if (cls == TMG_REGISTER_OUTPUT) { - for (int i = 0; i < clkInfoCount; i++) { - auto info = ctx->getPortClockingInfo(ci, name, i); - if (!ci->ports.count(info.clock_port) || ci->ports.at(info.clock_port).net == nullptr) - continue; - pd.cell_arcs.emplace_back(CellArc::CLK_TO_Q, info.clock_port, info.clockToQ, info.edge); - } - } - // Combinational delays through cell - for (auto &other_port : ci->ports) { - auto &op = other_port.second; - // ignore dangling ports and non-inputs - if (op.net == nullptr || op.type != PORT_IN) - continue; - DelayQuad delay; - bool is_path = ctx->getCellDelay(ci, other_port.first, name, delay); - if (is_path) - pd.cell_arcs.emplace_back(CellArc::COMBINATIONAL, other_port.first, delay); - } - } - } -} - -void TimingAnalyser::get_route_delays() -{ - for (auto &net : ctx->nets) { - NetInfo *ni = net.second.get(); - if (ni->driver.cell == nullptr || ni->driver.cell->bel == BelId()) - continue; - for (auto &usr : ni->users) { - if (usr.cell->bel == BelId()) - continue; - ports.at(CellPortKey(usr)).route_delay = DelayPair(ctx->getNetinfoRouteDelay(ni, usr)); - } - } -} - -void TimingAnalyser::set_route_delay(CellPortKey port, DelayPair value) { ports.at(port).route_delay = value; } - -void TimingAnalyser::topo_sort() -{ - TopoSort topo; - for (auto &port : ports) { - auto &pd = port.second; - // All ports are nodes - topo.node(port.first); - if (pd.type == PORT_IN) { - // inputs: combinational arcs through the cell are edges - for (auto &arc : pd.cell_arcs) { - if (arc.type != CellArc::COMBINATIONAL) - continue; - topo.edge(port.first, CellPortKey(port.first.cell, arc.other_port)); - } - } else if (pd.type == PORT_OUT) { - // output: routing arcs are edges - const NetInfo *pn = port_info(port.first).net; - if (pn != nullptr) { - for (auto &usr : pn->users) - topo.edge(port.first, CellPortKey(usr)); - } - } - } - bool no_loops = topo.sort(); - if (!no_loops && verbose_mode) { - log_info("Found %d combinational loops:\n", int(topo.loops.size())); - int i = 0; - for (auto &loop : topo.loops) { - log_info(" loop %d:\n", ++i); - for (auto &port : loop) { - log_info(" %s.%s (%s)\n", ctx->nameOf(port.cell), ctx->nameOf(port.port), - ctx->nameOf(port_info(port).net)); - } - } - } - have_loops = !no_loops; - std::swap(topological_order, topo.sorted); -} - -void TimingAnalyser::setup_port_domains() -{ - for (auto &d : domains) { - d.startpoints.clear(); - d.endpoints.clear(); - } - // Go forward through the topological order (domains from the PoV of arrival time) - bool first_iter = true; - do { - updated_domains = false; - for (auto port : topological_order) { - auto &pd = ports.at(port); - auto &pi = port_info(port); - if (pi.type == PORT_OUT) { - if (first_iter) { - for (auto &fanin : pd.cell_arcs) { - if (fanin.type != CellArc::CLK_TO_Q) - continue; - // registered outputs are startpoints - auto dom = domain_id(port.cell, fanin.other_port, fanin.edge); - // create per-domain data - pd.arrival[dom]; - domains.at(dom).startpoints.emplace_back(port, fanin.other_port); - } - } - // copy domains across routing - if (pi.net != nullptr) - for (auto &usr : pi.net->users) - copy_domains(port, CellPortKey(usr), false); - } else { - // copy domains from input to output - for (auto &fanout : pd.cell_arcs) { - if (fanout.type != CellArc::COMBINATIONAL) - continue; - copy_domains(port, CellPortKey(port.cell, fanout.other_port), false); - } - } - } - // Go backward through the topological order (domains from the PoV of required time) - for (auto port : reversed_range(topological_order)) { - auto &pd = ports.at(port); - auto &pi = port_info(port); - if (pi.type == PORT_OUT) { - // copy domains from output to input - for (auto &fanin : pd.cell_arcs) { - if (fanin.type != CellArc::COMBINATIONAL) - continue; - copy_domains(port, CellPortKey(port.cell, fanin.other_port), true); - } - } else { - if (first_iter) { - for (auto &fanout : pd.cell_arcs) { - if (fanout.type != CellArc::SETUP) - continue; - // registered inputs are endpoints - auto dom = domain_id(port.cell, fanout.other_port, fanout.edge); - // create per-domain data - pd.required[dom]; - domains.at(dom).endpoints.emplace_back(port, fanout.other_port); - } - } - // copy port to driver - if (pi.net != nullptr && pi.net->driver.cell != nullptr) - copy_domains(port, CellPortKey(pi.net->driver), true); - } - } - // Iterate over ports and find domain paris - for (auto port : topological_order) { - auto &pd = ports.at(port); - for (auto &arr : pd.arrival) - for (auto &req : pd.required) { - pd.domain_pairs[domain_pair_id(arr.first, req.first)]; - } - } - first_iter = false; - // If there are loops, repeat the process until a fixed point is reached, as there might be unusual ways to - // visit points, which would result in a missing domain key and therefore crash later on - } while (have_loops && updated_domains); - for (auto &dp : domain_pairs) { - auto &launch_data = domains.at(dp.key.launch); - auto &capture_data = domains.at(dp.key.capture); - if (launch_data.key.clock != capture_data.key.clock) - continue; - IdString clk = launch_data.key.clock; - delay_t period = ctx->getDelayFromNS(1.0e9 / ctx->setting("target_freq")); - if (ctx->nets.count(clk)) { - NetInfo *clk_net = ctx->nets.at(clk).get(); - if (clk_net->clkconstr) { - period = clk_net->clkconstr->period.minDelay(); - } - } - if (launch_data.key.edge != capture_data.key.edge) - period /= 2; - dp.period = DelayPair(period); - } -} - -void TimingAnalyser::reset_times() -{ - for (auto &port : ports) { - auto do_reset = [&](dict ×) { - for (auto &t : times) { - t.second.value = init_delay; - t.second.path_length = 0; - t.second.bwd_min = CellPortKey(); - t.second.bwd_max = CellPortKey(); - } - }; - do_reset(port.second.arrival); - do_reset(port.second.required); - for (auto &dp : port.second.domain_pairs) { - dp.second.setup_slack = std::numeric_limits::max(); - dp.second.hold_slack = std::numeric_limits::max(); - dp.second.max_path_length = 0; - dp.second.criticality = 0; - dp.second.budget = 0; - } - port.second.worst_crit = 0; - port.second.worst_setup_slack = std::numeric_limits::max(); - port.second.worst_hold_slack = std::numeric_limits::max(); - } -} - -void TimingAnalyser::set_arrival_time(CellPortKey target, domain_id_t domain, DelayPair arrival, int path_length, - CellPortKey prev) -{ - auto &arr = ports.at(target).arrival.at(domain); - if (arrival.max_delay > arr.value.max_delay) { - arr.value.max_delay = arrival.max_delay; - arr.bwd_max = prev; - } - if (!setup_only && (arrival.min_delay < arr.value.min_delay)) { - arr.value.min_delay = arrival.min_delay; - arr.bwd_min = prev; - } - arr.path_length = std::max(arr.path_length, path_length); -} - -void TimingAnalyser::set_required_time(CellPortKey target, domain_id_t domain, DelayPair required, int path_length, - CellPortKey prev) -{ - auto &req = ports.at(target).required.at(domain); - if (required.min_delay < req.value.min_delay) { - req.value.min_delay = required.min_delay; - req.bwd_min = prev; - } - if (!setup_only && (required.max_delay > req.value.max_delay)) { - req.value.max_delay = required.max_delay; - req.bwd_max = prev; - } - req.path_length = std::max(req.path_length, path_length); -} - -void TimingAnalyser::walk_forward() -{ - // Assign initial arrival time to domain startpoints - for (domain_id_t dom_id = 0; dom_id < domain_id_t(domains.size()); ++dom_id) { - auto &dom = domains.at(dom_id); - for (auto &sp : dom.startpoints) { - auto &pd = ports.at(sp.first); - DelayPair init_arrival(0); - CellPortKey clock_key; - // TODO: clock routing delay, if analysis of that is enabled - if (sp.second != IdString()) { - // clocked startpoints have a clock-to-out time - for (auto &fanin : pd.cell_arcs) { - if (fanin.type == CellArc::CLK_TO_Q && fanin.other_port == sp.second) { - init_arrival = init_arrival + fanin.value.delayPair(); - break; - } - } - clock_key = CellPortKey(sp.first.cell, sp.second); - } - set_arrival_time(sp.first, dom_id, init_arrival, 1, clock_key); - } - } - // Walk forward in topological order - for (auto p : topological_order) { - auto &pd = ports.at(p); - for (auto &arr : pd.arrival) { - if (pd.type == PORT_OUT) { - // Output port: propagate delay through net, adding route delay - NetInfo *net = port_info(p).net; - if (net != nullptr) - for (auto &usr : net->users) { - CellPortKey usr_key(usr); - auto &usr_pd = ports.at(usr_key); - set_arrival_time(usr_key, arr.first, arr.second.value + usr_pd.route_delay, - arr.second.path_length, p); - } - } else if (pd.type == PORT_IN) { - // Input port; propagate delay through cell, adding combinational delay - for (auto &fanout : pd.cell_arcs) { - if (fanout.type != CellArc::COMBINATIONAL) - continue; - set_arrival_time(CellPortKey(p.cell, fanout.other_port), arr.first, - arr.second.value + fanout.value.delayPair(), arr.second.path_length + 1, p); - } - } - } - } -} - -void TimingAnalyser::walk_backward() -{ - // Assign initial required time to domain endpoints - // Note that clock frequency will be considered later in the analysis for, for now all required times are normalised - // to 0ns - for (domain_id_t dom_id = 0; dom_id < domain_id_t(domains.size()); ++dom_id) { - auto &dom = domains.at(dom_id); - for (auto &ep : dom.endpoints) { - auto &pd = ports.at(ep.first); - DelayPair init_setuphold(0); - CellPortKey clock_key; - // TODO: clock routing delay, if analysis of that is enabled - if (ep.second != IdString()) { - // Add setup/hold time, if this endpoint is clocked - for (auto &fanin : pd.cell_arcs) { - if (fanin.type == CellArc::SETUP && fanin.other_port == ep.second) - init_setuphold.min_delay -= fanin.value.maxDelay(); - if (fanin.type == CellArc::HOLD && fanin.other_port == ep.second) - init_setuphold.max_delay -= fanin.value.maxDelay(); - } - clock_key = CellPortKey(ep.first.cell, ep.second); - } - set_required_time(ep.first, dom_id, init_setuphold, 1, clock_key); - } - } - // Walk backwards in topological order - for (auto p : reversed_range(topological_order)) { - auto &pd = ports.at(p); - for (auto &req : pd.required) { - if (pd.type == PORT_IN) { - // Input port: propagate delay back through net, subtracting route delay - NetInfo *net = port_info(p).net; - if (net != nullptr && net->driver.cell != nullptr) - set_required_time(CellPortKey(net->driver), req.first, - req.second.value - DelayPair(pd.route_delay.maxDelay()), req.second.path_length, - p); - } else if (pd.type == PORT_OUT) { - // Output port : propagate delay back through cell, subtracting combinational delay - for (auto &fanin : pd.cell_arcs) { - if (fanin.type != CellArc::COMBINATIONAL) - continue; - set_required_time(CellPortKey(p.cell, fanin.other_port), req.first, - req.second.value - DelayPair(fanin.value.maxDelay()), req.second.path_length + 1, - p); - } - } - } - } -} - -void TimingAnalyser::print_fmax() -{ - // Temporary testing code for comparison only - dict domain_fmax; - for (auto p : topological_order) { - auto &pd = ports.at(p); - for (auto &req : pd.required) { - if (pd.arrival.count(req.first)) { - auto &arr = pd.arrival.at(req.first); - double fmax = 1000.0 / ctx->getDelayNS(arr.value.maxDelay() - req.second.value.minDelay()); - if (!domain_fmax.count(req.first) || domain_fmax.at(req.first) > fmax) - domain_fmax[req.first] = fmax; - } - } - } - for (auto &fm : domain_fmax) { - log_info("Domain %s Worst Fmax %.02f\n", ctx->nameOf(domains.at(fm.first).key.clock), fm.second); - } -} - -void TimingAnalyser::compute_slack() -{ - for (auto &dp : domain_pairs) { - dp.worst_setup_slack = std::numeric_limits::max(); - dp.worst_hold_slack = std::numeric_limits::max(); - } - for (auto p : topological_order) { - auto &pd = ports.at(p); - for (auto &pdp : pd.domain_pairs) { - auto &dp = domain_pairs.at(pdp.first); - auto &arr = pd.arrival.at(dp.key.launch); - auto &req = pd.required.at(dp.key.capture); - pdp.second.setup_slack = 0 - (arr.value.maxDelay() - req.value.minDelay()); - if (!setup_only) - pdp.second.hold_slack = arr.value.minDelay() - req.value.maxDelay(); - pdp.second.max_path_length = arr.path_length + req.path_length; - if (dp.key.launch == dp.key.capture) - pd.worst_setup_slack = std::min(pd.worst_setup_slack, dp.period.minDelay() + pdp.second.setup_slack); - dp.worst_setup_slack = std::min(dp.worst_setup_slack, pdp.second.setup_slack); - if (!setup_only) { - pd.worst_hold_slack = std::min(pd.worst_hold_slack, pdp.second.hold_slack); - dp.worst_hold_slack = std::min(dp.worst_hold_slack, pdp.second.hold_slack); - } - } - } -} - -void TimingAnalyser::compute_criticality() -{ - for (auto p : topological_order) { - auto &pd = ports.at(p); - for (auto &pdp : pd.domain_pairs) { - auto &dp = domain_pairs.at(pdp.first); - float crit = - 1.0f - (float(pdp.second.setup_slack) - float(dp.worst_setup_slack)) / float(-dp.worst_setup_slack); - crit = std::min(crit, 1.0f); - crit = std::max(crit, 0.0f); - pdp.second.criticality = crit; - pd.worst_crit = std::max(pd.worst_crit, crit); - } - } -} - -std::vector TimingAnalyser::get_failing_eps(domain_id_t domain_pair, int count) -{ - std::vector failing_eps; - delay_t last_slack = std::numeric_limits::min(); - auto &dp = domain_pairs.at(domain_pair); - auto &cap_d = domains.at(dp.key.capture); - while (int(failing_eps.size()) < count) { - CellPortKey next; - delay_t next_slack = std::numeric_limits::max(); - for (auto ep : cap_d.endpoints) { - auto &pd = ports.at(ep.first); - if (!pd.domain_pairs.count(domain_pair)) - continue; - delay_t ep_slack = pd.domain_pairs.at(domain_pair).setup_slack; - if (ep_slack < next_slack && ep_slack > last_slack) { - next = ep.first; - next_slack = ep_slack; - } - } - if (next == CellPortKey()) - break; - failing_eps.push_back(next); - last_slack = next_slack; - } - return failing_eps; -} - -void TimingAnalyser::print_critical_path(CellPortKey endpoint, domain_id_t domain_pair) -{ - CellPortKey cursor = endpoint; - auto &dp = domain_pairs.at(domain_pair); - log(" endpoint %s.%s (slack %.02fns):\n", ctx->nameOf(cursor.cell), ctx->nameOf(cursor.port), - ctx->getDelayNS(ports.at(cursor).domain_pairs.at(domain_pair).setup_slack)); - while (cursor != CellPortKey()) { - log(" %s.%s (net %s)\n", ctx->nameOf(cursor.cell), ctx->nameOf(cursor.port), - ctx->nameOf(get_net_or_empty(ctx->cells.at(cursor.cell).get(), cursor.port))); - if (!ports.at(cursor).arrival.count(dp.key.launch)) - break; - cursor = ports.at(cursor).arrival.at(dp.key.launch).bwd_max; - } -} - -namespace { -const char *edge_name(ClockEdge edge) { return (edge == FALLING_EDGE) ? "negedge" : "posedge"; } -} // namespace - -void TimingAnalyser::print_report() -{ - for (int i = 0; i < int(domain_pairs.size()); i++) { - auto &dp = domain_pairs.at(i); - auto &launch = domains.at(dp.key.launch); - auto &capture = domains.at(dp.key.capture); - log("Worst endpoints for %s %s -> %s %s\n", edge_name(launch.key.edge), ctx->nameOf(launch.key.clock), - edge_name(capture.key.edge), ctx->nameOf(capture.key.clock)); - auto failing_eps = get_failing_eps(i, 5); - for (auto &ep : failing_eps) - print_critical_path(ep, i); - log_break(); - } -} - -domain_id_t TimingAnalyser::domain_id(IdString cell, IdString clock_port, ClockEdge edge) -{ - return domain_id(ctx->cells.at(cell)->ports.at(clock_port).net, edge); -} -domain_id_t TimingAnalyser::domain_id(const NetInfo *net, ClockEdge edge) -{ - NPNR_ASSERT(net != nullptr); - ClockDomainKey key{net->name, edge}; - auto inserted = domain_to_id.emplace(key, domains.size()); - if (inserted.second) { - domains.emplace_back(key); - } - return inserted.first->second; -} -domain_id_t TimingAnalyser::domain_pair_id(domain_id_t launch, domain_id_t capture) -{ - ClockDomainPairKey key{launch, capture}; - auto inserted = pair_to_id.emplace(key, domain_pairs.size()); - if (inserted.second) { - domain_pairs.emplace_back(key); - } - return inserted.first->second; -} - -void TimingAnalyser::copy_domains(const CellPortKey &from, const CellPortKey &to, bool backward) -{ - auto &f = ports.at(from), &t = ports.at(to); - for (auto &dom : (backward ? f.required : f.arrival)) { - updated_domains |= (backward ? t.required : t.arrival).emplace(dom.first, ArrivReqTime{}).second; - } -} - -CellInfo *TimingAnalyser::cell_info(const CellPortKey &key) { return ctx->cells.at(key.cell).get(); } - -PortInfo &TimingAnalyser::port_info(const CellPortKey &key) { return ctx->cells.at(key.cell)->ports.at(key.port); } - -/** LEGACY CODE BEGIN **/ - -typedef std::vector PortRefVector; -typedef std::map DelayFrequency; - -struct CriticalPathData -{ - PortRefVector ports; - delay_t path_delay; - delay_t path_period; -}; - -typedef dict CriticalPathDataMap; - -typedef dict> DetailedNetTimings; - -struct Timing -{ - Context *ctx; - bool net_delays; - bool update; - delay_t min_slack; - CriticalPathDataMap *crit_path; - DelayFrequency *slack_histogram; - DetailedNetTimings *detailed_net_timings; - IdString async_clock; - - struct TimingData - { - TimingData() : max_arrival(), max_path_length(), min_remaining_budget() {} - TimingData(delay_t max_arrival) : max_arrival(max_arrival), max_path_length(), min_remaining_budget() {} - delay_t max_arrival; - unsigned max_path_length = 0; - delay_t min_remaining_budget; - bool false_startpoint = false; - std::vector min_required; - dict arrival_time; - }; - - Timing(Context *ctx, bool net_delays, bool update, CriticalPathDataMap *crit_path = nullptr, - DelayFrequency *slack_histogram = nullptr, DetailedNetTimings *detailed_net_timings = nullptr) - : ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->setting("target_freq")), - crit_path(crit_path), slack_histogram(slack_histogram), detailed_net_timings(detailed_net_timings), - async_clock(ctx->id("$async$")) - { - } - - delay_t walk_paths() - { - const auto clk_period = ctx->getDelayFromNS(1.0e9 / ctx->setting("target_freq")); - - // First, compute the topological order of nets to walk through the circuit, assuming it is a _acyclic_ graph - // TODO(eddieh): Handle the case where it is cyclic, e.g. combinatorial loops - std::vector topological_order; - dict, hash_ptr_ops> net_data; - // In lieu of deleting edges from the graph, simply count the number of fanins to each output port - dict port_fanin; - - std::vector input_ports; - std::vector output_ports; - - pool ooc_port_nets; - - // In out-of-context mode, top-level inputs look floating but aren't - if (bool_or_default(ctx->settings, ctx->id("arch.ooc"))) { - for (auto &p : ctx->ports) { - if (p.second.type != PORT_IN || p.second.net == nullptr) - continue; - ooc_port_nets.insert(p.second.net->name); - } - } - - for (auto &cell : ctx->cells) { - input_ports.clear(); - output_ports.clear(); - for (auto &port : cell.second->ports) { - if (!port.second.net) - continue; - if (port.second.type == PORT_OUT) - output_ports.push_back(&port.second); - else - input_ports.push_back(port.first); - } - - for (auto o : output_ports) { - int clocks = 0; - TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clocks); - // If output port is influenced by a clock (e.g. FF output) then add it to the ordering as a timing - // start-point - if (portClass == TMG_REGISTER_OUTPUT) { - topological_order.emplace_back(o->net); - for (int i = 0; i < clocks; i++) { - TimingClockingInfo clkInfo = ctx->getPortClockingInfo(cell.second.get(), o->name, i); - const NetInfo *clknet = get_net_or_empty(cell.second.get(), clkInfo.clock_port); - IdString clksig = clknet ? clknet->name : async_clock; - net_data[o->net][ClockEvent{clksig, clknet ? clkInfo.edge : RISING_EDGE}] = - TimingData{clkInfo.clockToQ.maxDelay()}; - } - - } else { - if (portClass == TMG_STARTPOINT || portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE) { - topological_order.emplace_back(o->net); - TimingData td; - td.false_startpoint = (portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE); - td.max_arrival = 0; - net_data[o->net][ClockEvent{async_clock, RISING_EDGE}] = td; - } - - // Don't analyse paths from a clock input to other pins - they will be considered by the - // special-case handling register input/output class ports - if (portClass == TMG_CLOCK_INPUT) - continue; - - // Otherwise, for all driven input ports on this cell, if a timing arc exists between the input and - // the current output port, increment fanin counter - for (auto i : input_ports) { - DelayQuad comb_delay; - NetInfo *i_net = cell.second->ports[i].net; - if (i_net->driver.cell == nullptr && !ooc_port_nets.count(i_net->name)) - continue; - bool is_path = ctx->getCellDelay(cell.second.get(), i, o->name, comb_delay); - if (is_path) - port_fanin[o]++; - } - // If there is no fanin, add the port as a false startpoint - if (!port_fanin.count(o) && !net_data.count(o->net)) { - topological_order.emplace_back(o->net); - TimingData td; - td.false_startpoint = true; - td.max_arrival = 0; - net_data[o->net][ClockEvent{async_clock, RISING_EDGE}] = td; - } - } - } - } - - // In out-of-context mode, handle top-level ports correctly - if (bool_or_default(ctx->settings, ctx->id("arch.ooc"))) { - for (auto &p : ctx->ports) { - if (p.second.type != PORT_IN || p.second.net == nullptr) - continue; - topological_order.emplace_back(p.second.net); - } - } - - std::deque queue(topological_order.begin(), topological_order.end()); - // Now walk the design, from the start points identified previously, building up a topological order - while (!queue.empty()) { - const auto net = queue.front(); - queue.pop_front(); - - for (auto &usr : net->users) { - int user_clocks; - TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, user_clocks); - if (usrClass == TMG_IGNORE || usrClass == TMG_CLOCK_INPUT) - continue; - for (auto &port : usr.cell->ports) { - if (port.second.type != PORT_OUT || !port.second.net) - continue; - int port_clocks; - TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, port_clocks); - - // Skip if this is a clocked output (but allow non-clocked ones) - if (portClass == TMG_REGISTER_OUTPUT || portClass == TMG_STARTPOINT || portClass == TMG_IGNORE || - portClass == TMG_GEN_CLOCK) - continue; - DelayQuad comb_delay; - bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); - if (!is_path) - continue; - // Decrement the fanin count, and only add to topological order if all its fanins have already - // been visited - auto it = port_fanin.find(&port.second); - if (it == port_fanin.end()) - log_error("Timing counted negative fanin count for port %s.%s (net %s), please report this " - "error.\n", - ctx->nameOf(usr.cell), ctx->nameOf(port.first), ctx->nameOf(port.second.net)); - if (--it->second == 0) { - topological_order.emplace_back(port.second.net); - queue.emplace_back(port.second.net); - port_fanin.erase(it); - } - } - } - } - - // Sanity check to ensure that all ports where fanins were recorded were indeed visited - if (!port_fanin.empty() && !bool_or_default(ctx->settings, ctx->id("timing/ignoreLoops"), false)) { - for (auto fanin : port_fanin) { - NetInfo *net = fanin.first->net; - if (net != nullptr) { - log_info(" remaining fanin includes %s (net %s)\n", fanin.first->name.c_str(ctx), - net->name.c_str(ctx)); - if (net->driver.cell != nullptr) - log_info(" driver = %s.%s\n", net->driver.cell->name.c_str(ctx), - net->driver.port.c_str(ctx)); - for (auto net_user : net->users) - log_info(" user: %s.%s\n", net_user.cell->name.c_str(ctx), net_user.port.c_str(ctx)); - } else { - log_info(" remaining fanin includes %s (no net)\n", fanin.first->name.c_str(ctx)); - } - } - if (ctx->force) - log_warning("timing analysis failed due to presence of combinatorial loops, incomplete specification " - "of timing ports, etc.\n"); - else - log_error("timing analysis failed due to presence of combinatorial loops, incomplete specification of " - "timing ports, etc.\n"); - } - - // Go forwards topologically to find the maximum arrival time and max path length for each net - std::vector startdomains; - for (auto net : topological_order) { - if (!net_data.count(net)) - continue; - // Updates later on might invalidate a reference taken here to net_data, so iterate over a list of domains - // instead - startdomains.clear(); - { - auto &nd_map = net_data.at(net); - for (auto &startdomain : nd_map) - startdomains.push_back(startdomain.first); - } - for (auto &start_clk : startdomains) { - auto &nd = net_data.at(net).at(start_clk); - if (nd.false_startpoint) - continue; - const auto net_arrival = nd.max_arrival; - const auto net_length_plus_one = nd.max_path_length + 1; - nd.min_remaining_budget = clk_period; - for (auto &usr : net->users) { - int port_clocks; - TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks); - auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); - auto usr_arrival = net_arrival + net_delay; - - if (portClass == TMG_ENDPOINT || portClass == TMG_IGNORE || portClass == TMG_CLOCK_INPUT) { - // Skip - } else { - auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); - // Iterate over all output ports on the same cell as the sink - for (auto port : usr.cell->ports) { - if (port.second.type != PORT_OUT || !port.second.net) - continue; - DelayQuad comb_delay; - // Look up delay through this path - bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); - if (!is_path) - continue; - auto &data = net_data[port.second.net][start_clk]; - auto &arrival = data.max_arrival; - arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay()); - if (!budget_override) { // Do not increment path length if budget overridden since it - // doesn't - // require a share of the slack - auto &path_length = data.max_path_length; - path_length = std::max(path_length, net_length_plus_one); - } - } - } - } - } - } - - dict> crit_nets; - - // Now go backwards topologically to determine the minimum path slack, and to distribute all path slack evenly - // between all nets on the path - for (auto net : boost::adaptors::reverse(topological_order)) { - if (!net_data.count(net)) - continue; - auto &nd_map = net_data.at(net); - for (auto &startdomain : nd_map) { - auto &nd = startdomain.second; - // Ignore false startpoints - if (nd.false_startpoint) - continue; - const delay_t net_length_plus_one = nd.max_path_length + 1; - auto &net_min_remaining_budget = nd.min_remaining_budget; - for (auto &usr : net->users) { - auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); - auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); - int port_clocks; - TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks); - if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) { - auto process_endpoint = [&](IdString clksig, ClockEdge edge, delay_t setup) { - const auto net_arrival = nd.max_arrival; - const auto endpoint_arrival = net_arrival + net_delay + setup; - delay_t period; - // Set default period - if (edge == startdomain.first.edge) { - period = clk_period; - } else { - period = clk_period / 2; - } - if (clksig != async_clock) { - if (ctx->nets.at(clksig)->clkconstr) { - if (edge == startdomain.first.edge) { - // same edge - period = ctx->nets.at(clksig)->clkconstr->period.minDelay(); - } else if (edge == RISING_EDGE) { - // falling -> rising - period = ctx->nets.at(clksig)->clkconstr->low.minDelay(); - } else if (edge == FALLING_EDGE) { - // rising -> falling - period = ctx->nets.at(clksig)->clkconstr->high.minDelay(); - } - } - } - auto path_budget = period - endpoint_arrival; - - if (update) { - auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; - usr.budget = std::min(usr.budget, net_delay + budget_share); - net_min_remaining_budget = - std::min(net_min_remaining_budget, path_budget - budget_share); - } - - if (path_budget < min_slack) - min_slack = path_budget; - - if (slack_histogram) { - int slack_ps = ctx->getDelayNS(path_budget) * 1000; - (*slack_histogram)[slack_ps]++; - } - ClockEvent dest_ev{clksig, edge}; - ClockPair clockPair{startdomain.first, dest_ev}; - nd.arrival_time[dest_ev] = std::max(nd.arrival_time[dest_ev], endpoint_arrival); - - // Store the detailed timing for each net and user (a.k.a. sink) - if (detailed_net_timings) { - NetSinkTiming sink_timing; - sink_timing.clock_pair = clockPair; - sink_timing.cell_port = std::make_pair(usr.cell->name, usr.port); - sink_timing.delay = endpoint_arrival; - sink_timing.budget = period; - - (*detailed_net_timings)[net->name].push_back(sink_timing); - } - - if (crit_path) { - if (!crit_nets.count(clockPair) || crit_nets.at(clockPair).first < endpoint_arrival) { - crit_nets[clockPair] = std::make_pair(endpoint_arrival, net); - (*crit_path)[clockPair].path_delay = endpoint_arrival; - (*crit_path)[clockPair].path_period = period; - (*crit_path)[clockPair].ports.clear(); - (*crit_path)[clockPair].ports.push_back(&usr); - } - } - }; - if (portClass == TMG_REGISTER_INPUT) { - for (int i = 0; i < port_clocks; i++) { - TimingClockingInfo clkInfo = ctx->getPortClockingInfo(usr.cell, usr.port, i); - const NetInfo *clknet = get_net_or_empty(usr.cell, clkInfo.clock_port); - IdString clksig = clknet ? clknet->name : async_clock; - process_endpoint(clksig, clknet ? clkInfo.edge : RISING_EDGE, clkInfo.setup.maxDelay()); - } - } else { - process_endpoint(async_clock, RISING_EDGE, 0); - } - - } else if (update) { - - // Iterate over all output ports on the same cell as the sink - for (const auto &port : usr.cell->ports) { - if (port.second.type != PORT_OUT || !port.second.net) - continue; - DelayQuad comb_delay; - bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); - if (!is_path) - continue; - if (net_data.count(port.second.net) && - net_data.at(port.second.net).count(startdomain.first)) { - auto path_budget = - net_data.at(port.second.net).at(startdomain.first).min_remaining_budget; - auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; - usr.budget = std::min(usr.budget, net_delay + budget_share); - net_min_remaining_budget = - std::min(net_min_remaining_budget, path_budget - budget_share); - } - } - } - } - } - } - - if (crit_path) { - // Walk backwards from the most critical net - for (auto crit_pair : crit_nets) { - NetInfo *crit_net = crit_pair.second.second; - auto &cp_ports = (*crit_path)[crit_pair.first].ports; - while (crit_net) { - const PortInfo *crit_ipin = nullptr; - delay_t max_arrival = std::numeric_limits::min(); - // Look at all input ports on its driving cell - for (const auto &port : crit_net->driver.cell->ports) { - if (port.second.type != PORT_IN || !port.second.net) - continue; - DelayQuad comb_delay; - bool is_path = - ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay); - if (!is_path) - continue; - // If input port is influenced by a clock, skip - int port_clocks; - TimingPortClass portClass = - ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks); - if (portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) - continue; - // And find the fanin net with the latest arrival time - if (net_data.count(port.second.net) && - net_data.at(port.second.net).count(crit_pair.first.start)) { - auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival; - if (net_delays) { - for (auto &user : port.second.net->users) - if (user.port == port.first && user.cell == crit_net->driver.cell) { - net_arrival += ctx->getNetinfoRouteDelay(port.second.net, user); - break; - } - } - net_arrival += comb_delay.maxDelay(); - if (net_arrival > max_arrival) { - max_arrival = net_arrival; - crit_ipin = &port.second; - } - } - } - - if (!crit_ipin) - break; - // Now convert PortInfo* into a PortRef* - for (auto &usr : crit_ipin->net->users) { - if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) { - cp_ports.push_back(&usr); - break; - } - } - crit_net = crit_ipin->net; - } - std::reverse(cp_ports.begin(), cp_ports.end()); - } - } - return min_slack; - } - - void assign_budget() - { - // Clear delays to a very high value first - for (auto &net : ctx->nets) { - for (auto &usr : net.second->users) { - usr.budget = std::numeric_limits::max(); - } - } - - walk_paths(); - } -}; - -void assign_budget(Context *ctx, bool quiet) -{ - if (!quiet) { - log_break(); - log_info("Annotating ports with timing budgets for target frequency %.2f MHz\n", - ctx->setting("target_freq") / 1e6); - } - - Timing timing(ctx, ctx->setting("slack_redist_iter") > 0 /* net_delays */, true /* update */); - timing.assign_budget(); - - if (!quiet || ctx->verbose) { - for (auto &net : ctx->nets) { - for (auto &user : net.second->users) { - // Post-update check - if (!ctx->setting("auto_freq") && user.budget < 0) - log_info("port %s.%s, connected to net '%s', has negative " - "timing budget of %fns\n", - user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx), - ctx->getDelayNS(user.budget)); - else if (ctx->debug) - log_info("port %s.%s, connected to net '%s', has " - "timing budget of %fns\n", - user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx), - ctx->getDelayNS(user.budget)); - } - } - } - - // For slack redistribution, if user has not specified a frequency dynamically adjust the target frequency to be the - // currently achieved maximum - if (ctx->setting("auto_freq") && ctx->setting("slack_redist_iter") > 0) { - delay_t default_slack = delay_t((1.0e9 / ctx->getDelayNS(1)) / ctx->setting("target_freq")); - ctx->settings[ctx->id("target_freq")] = - std::to_string(1.0e9 / ctx->getDelayNS(default_slack - timing.min_slack)); - if (ctx->verbose) - log_info("minimum slack for this assign = %.2f ns, target Fmax for next " - "update = %.2f MHz\n", - ctx->getDelayNS(timing.min_slack), ctx->setting("target_freq") / 1e6); - } - - if (!quiet) - log_info("Checksum: 0x%08x\n", ctx->checksum()); -} - -CriticalPath build_critical_path_report(Context *ctx, ClockPair &clocks, const PortRefVector &crit_path) -{ - - CriticalPath report; - report.clock_pair = clocks; - - auto &front = crit_path.front(); - auto &front_port = front->cell->ports.at(front->port); - auto &front_driver = front_port.net->driver; - - int port_clocks; - auto portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks); - - const CellInfo *last_cell = front->cell; - IdString last_port = front_driver.port; - - int clock_start = -1; - if (portClass == TMG_REGISTER_OUTPUT) { - for (int i = 0; i < port_clocks; i++) { - TimingClockingInfo clockInfo = ctx->getPortClockingInfo(front_driver.cell, front_driver.port, i); - const NetInfo *clknet = get_net_or_empty(front_driver.cell, clockInfo.clock_port); - if (clknet != nullptr && clknet->name == clocks.start.clock && clockInfo.edge == clocks.start.edge) { - last_port = clockInfo.clock_port; - clock_start = i; - break; - } - } - } - - for (auto sink : crit_path) { - auto sink_cell = sink->cell; - auto &port = sink_cell->ports.at(sink->port); - auto net = port.net; - auto &driver = net->driver; - auto driver_cell = driver.cell; - - CriticalPath::Segment seg_logic; - - DelayQuad comb_delay; - if (clock_start != -1) { - auto clockInfo = ctx->getPortClockingInfo(driver_cell, driver.port, clock_start); - comb_delay = clockInfo.clockToQ; - clock_start = -1; - seg_logic.type = CriticalPath::Segment::Type::CLK_TO_Q; - } else if (last_port == driver.port) { - // Case where we start with a STARTPOINT etc - comb_delay = DelayQuad(0); - seg_logic.type = CriticalPath::Segment::Type::SOURCE; - } else { - ctx->getCellDelay(driver_cell, last_port, driver.port, comb_delay); - seg_logic.type = CriticalPath::Segment::Type::LOGIC; - } - - seg_logic.delay = comb_delay.maxDelay(); - seg_logic.budget = 0; - seg_logic.from = std::make_pair(last_cell->name, last_port); - seg_logic.to = std::make_pair(driver_cell->name, driver.port); - seg_logic.net = IdString(); - report.segments.push_back(seg_logic); - - auto net_delay = ctx->getNetinfoRouteDelay(net, *sink); - - CriticalPath::Segment seg_route; - seg_route.type = CriticalPath::Segment::Type::ROUTING; - seg_route.delay = net_delay; - seg_route.budget = sink->budget; - seg_route.from = std::make_pair(driver_cell->name, driver.port); - seg_route.to = std::make_pair(sink_cell->name, sink->port); - seg_route.net = net->name; - report.segments.push_back(seg_route); - - last_cell = sink_cell; - last_port = sink->port; - } - - int clockCount = 0; - auto sinkClass = ctx->getPortTimingClass(crit_path.back()->cell, crit_path.back()->port, clockCount); - if (sinkClass == TMG_REGISTER_INPUT && clockCount > 0) { - auto sinkClockInfo = ctx->getPortClockingInfo(crit_path.back()->cell, crit_path.back()->port, 0); - delay_t setup = sinkClockInfo.setup.maxDelay(); - - CriticalPath::Segment seg_logic; - seg_logic.type = CriticalPath::Segment::Type::SETUP; - seg_logic.delay = setup; - seg_logic.budget = 0; - seg_logic.from = std::make_pair(last_cell->name, last_port); - seg_logic.to = seg_logic.from; - seg_logic.net = IdString(); - report.segments.push_back(seg_logic); - } - - return report; -} - -void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path, bool warn_on_failure, - bool update_results) -{ - auto format_event = [ctx](const ClockEvent &e, int field_width = 0) { - std::string value; - if (e.clock == ctx->id("$async$")) - value = std::string(""); - else - value = (e.edge == FALLING_EDGE ? std::string("negedge ") : std::string("posedge ")) + e.clock.str(ctx); - if (int(value.length()) < field_width) - value.insert(value.length(), field_width - int(value.length()), ' '); - return value; - }; - - CriticalPathDataMap crit_paths; - DelayFrequency slack_histogram; - DetailedNetTimings detailed_net_timings; - - Timing timing(ctx, true /* net_delays */, false /* update */, (print_path || print_fmax) ? &crit_paths : nullptr, - print_histogram ? &slack_histogram : nullptr, - (update_results && ctx->detailed_timing_report) ? &detailed_net_timings : nullptr); - timing.walk_paths(); - - bool report_critical_paths = print_path || print_fmax || update_results; - - dict clock_reports; - std::vector xclock_reports; - dict clock_fmax; - std::set empty_clocks; // set of clocks with no interior paths - - if (report_critical_paths) { - - for (auto path : crit_paths) { - const ClockEvent &a = path.first.start; - const ClockEvent &b = path.first.end; - empty_clocks.insert(a.clock); - empty_clocks.insert(b.clock); - } - for (auto path : crit_paths) { - const ClockEvent &a = path.first.start; - const ClockEvent &b = path.first.end; - if (a.clock != b.clock || a.clock == ctx->id("$async$")) - continue; - double Fmax; - empty_clocks.erase(a.clock); - if (a.edge == b.edge) - Fmax = 1000 / ctx->getDelayNS(path.second.path_delay); - else - Fmax = 500 / ctx->getDelayNS(path.second.path_delay); - if (!clock_fmax.count(a.clock) || Fmax < clock_fmax.at(a.clock).achieved) { - clock_fmax[a.clock].achieved = Fmax; - clock_fmax[a.clock].constraint = 0.0f; // Will be filled later - clock_reports[a.clock] = build_critical_path_report(ctx, path.first, path.second.ports); - clock_reports[a.clock].period = path.second.path_period; - } - } - - for (auto &path : crit_paths) { - const ClockEvent &a = path.first.start; - const ClockEvent &b = path.first.end; - if (a.clock == b.clock && a.clock != ctx->id("$async$")) - continue; - - auto &crit_path = crit_paths.at(path.first).ports; - xclock_reports.push_back(build_critical_path_report(ctx, path.first, crit_path)); - xclock_reports.back().period = path.second.path_period; - } - - if (clock_reports.empty()) { - log_info("No Fmax available; no interior timing paths found in design.\n"); - } - - std::sort(xclock_reports.begin(), xclock_reports.end(), [ctx](const CriticalPath &ra, const CriticalPath &rb) { - const auto &a = ra.clock_pair; - const auto &b = rb.clock_pair; - - if (a.start.clock.str(ctx) < b.start.clock.str(ctx)) - return true; - if (a.start.clock.str(ctx) > b.start.clock.str(ctx)) - return false; - if (a.start.edge < b.start.edge) - return true; - if (a.start.edge > b.start.edge) - return false; - if (a.end.clock.str(ctx) < b.end.clock.str(ctx)) - return true; - if (a.end.clock.str(ctx) > b.end.clock.str(ctx)) - return false; - if (a.end.edge < b.end.edge) - return true; - return false; - }); - - for (auto &clock : clock_reports) { - float target = ctx->setting("target_freq") / 1e6; - if (ctx->nets.at(clock.first)->clkconstr) - target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay()); - clock_fmax[clock.first].constraint = target; - } - } - - // Print critical paths - if (print_path) { - - static auto print_net_source = [ctx](const NetInfo *net) { - // Check if this net is annotated with a source list - auto sources = net->attrs.find(ctx->id("src")); - if (sources == net->attrs.end()) { - // No sources for this net, can't print anything - return; - } - - // Sources are separated by pipe characters. - // There is no guaranteed ordering on sources, so we just print all - auto sourcelist = sources->second.as_string(); - std::vector source_entries; - size_t current = 0, prev = 0; - while ((current = sourcelist.find("|", prev)) != std::string::npos) { - source_entries.emplace_back(sourcelist.substr(prev, current - prev)); - prev = current + 1; - } - // Ensure we emplace the final entry - source_entries.emplace_back(sourcelist.substr(prev, current - prev)); - - // Iterate and print our source list at the correct indentation level - log_info(" Defined in:\n"); - for (auto entry : source_entries) { - log_info(" %s\n", entry.c_str()); - } - }; - - // A helper function for reporting one critical path - auto print_path_report = [ctx](const CriticalPath &path) { - delay_t total = 0, logic_total = 0, route_total = 0; - - log_info("curr total\n"); - for (const auto &segment : path.segments) { - - total += segment.delay; - - if (segment.type == CriticalPath::Segment::Type::CLK_TO_Q || - segment.type == CriticalPath::Segment::Type::SOURCE || - segment.type == CriticalPath::Segment::Type::LOGIC || - segment.type == CriticalPath::Segment::Type::SETUP) { - logic_total += segment.delay; - - const std::string type_name = - (segment.type == CriticalPath::Segment::Type::SETUP) ? "Setup" : "Source"; - - log_info("%4.1f %4.1f %s %s.%s\n", ctx->getDelayNS(segment.delay), ctx->getDelayNS(total), - type_name.c_str(), segment.to.first.c_str(ctx), segment.to.second.c_str(ctx)); - } else if (segment.type == CriticalPath::Segment::Type::ROUTING) { - route_total += segment.delay; - - const auto &driver = ctx->cells.at(segment.from.first); - const auto &sink = ctx->cells.at(segment.to.first); - - auto driver_loc = ctx->getBelLocation(driver->bel); - auto sink_loc = ctx->getBelLocation(sink->bel); - - log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n", ctx->getDelayNS(segment.delay), - ctx->getDelayNS(total), segment.net.c_str(ctx), ctx->getDelayNS(segment.budget), - driver_loc.x, driver_loc.y, sink_loc.x, sink_loc.y); - log_info(" Sink %s.%s\n", segment.to.first.c_str(ctx), segment.to.second.c_str(ctx)); - - const NetInfo *net = ctx->nets.at(segment.net).get(); - - if (ctx->verbose) { - - PortRef sink_ref; - sink_ref.cell = sink.get(); - sink_ref.port = segment.to.second; - sink_ref.budget = segment.budget; - - auto driver_wire = ctx->getNetinfoSourceWire(net); - auto sink_wire = ctx->getNetinfoSinkWire(net, sink_ref, 0); - log_info(" prediction: %f ns estimate: %f ns\n", - ctx->getDelayNS(ctx->predictArcDelay(net, sink_ref)), - ctx->getDelayNS(ctx->estimateDelay(driver_wire, sink_wire))); - auto cursor = sink_wire; - delay_t delay; - while (driver_wire != cursor) { -#ifdef ARCH_ECP5 - if (net->is_global) - break; -#endif - auto it = net->wires.find(cursor); - assert(it != net->wires.end()); - auto pip = it->second.pip; - NPNR_ASSERT(pip != PipId()); - delay = ctx->getPipDelay(pip).maxDelay(); - log_info(" %1.3f %s\n", ctx->getDelayNS(delay), ctx->nameOfPip(pip)); - cursor = ctx->getPipSrcWire(pip); - } - } - - if (!ctx->disable_critical_path_source_print) { - print_net_source(net); - } - } - } - log_info("%.1f ns logic, %.1f ns routing\n", ctx->getDelayNS(logic_total), ctx->getDelayNS(route_total)); - }; - - // Single domain paths - for (auto &clock : clock_reports) { - log_break(); - std::string start = clock.second.clock_pair.start.edge == FALLING_EDGE ? std::string("negedge") - : std::string("posedge"); - std::string end = - clock.second.clock_pair.end.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge"); - log_info("Critical path report for clock '%s' (%s -> %s):\n", clock.first.c_str(ctx), start.c_str(), - end.c_str()); - auto &report = clock.second; - print_path_report(report); - } - - // Cross-domain paths - for (auto &report : xclock_reports) { - log_break(); - std::string start = format_event(report.clock_pair.start); - std::string end = format_event(report.clock_pair.end); - log_info("Critical path report for cross-domain path '%s' -> '%s':\n", start.c_str(), end.c_str()); - print_path_report(report); - } - } - - if (print_fmax) { - log_break(); - - unsigned max_width = 0; - for (auto &clock : clock_reports) - max_width = std::max(max_width, clock.first.str(ctx).size()); - - for (auto &clock : clock_reports) { - const auto &clock_name = clock.first.str(ctx); - const int width = max_width - clock_name.size(); - - float fmax = clock_fmax[clock.first].achieved; - float target = clock_fmax[clock.first].constraint; - bool passed = target < fmax; - - if (!warn_on_failure || passed) - log_info("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "", - clock_name.c_str(), fmax, passed ? "PASS" : "FAIL", target); - else if (bool_or_default(ctx->settings, ctx->id("timing/allowFail"), false)) - log_warning("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "", - clock_name.c_str(), fmax, passed ? "PASS" : "FAIL", target); - else - log_nonfatal_error("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "", - clock_name.c_str(), fmax, passed ? "PASS" : "FAIL", target); - } - for (auto &eclock : empty_clocks) { - if (eclock != ctx->id("$async$")) - log_info("Clock '%s' has no interior paths\n", eclock.c_str(ctx)); - } - log_break(); - - int start_field_width = 0, end_field_width = 0; - for (auto &report : xclock_reports) { - start_field_width = std::max((int)format_event(report.clock_pair.start).length(), start_field_width); - end_field_width = std::max((int)format_event(report.clock_pair.end).length(), end_field_width); - } - - for (auto &report : xclock_reports) { - const ClockEvent &a = report.clock_pair.start; - const ClockEvent &b = report.clock_pair.end; - delay_t path_delay = 0; - for (const auto &segment : report.segments) { - path_delay += segment.delay; - } - auto ev_a = format_event(a, start_field_width), ev_b = format_event(b, end_field_width); - log_info("Max delay %s -> %s: %0.02f ns\n", ev_a.c_str(), ev_b.c_str(), ctx->getDelayNS(path_delay)); - } - log_break(); - } - - if (print_histogram && slack_histogram.size() > 0) { - unsigned num_bins = 20; - unsigned bar_width = 60; - auto min_slack = slack_histogram.begin()->first; - auto max_slack = slack_histogram.rbegin()->first; - auto bin_size = std::max(1, ceil((max_slack - min_slack + 1) / float(num_bins))); - std::vector bins(num_bins); - unsigned max_freq = 0; - for (const auto &i : slack_histogram) { - int bin_idx = int((i.first - min_slack) / bin_size); - if (bin_idx < 0) - bin_idx = 0; - else if (bin_idx >= int(num_bins)) - bin_idx = num_bins - 1; - auto &bin = bins.at(bin_idx); - bin += i.second; - max_freq = std::max(max_freq, bin); - } - bar_width = std::min(bar_width, max_freq); - - log_break(); - log_info("Slack histogram:\n"); - log_info(" legend: * represents %d endpoint(s)\n", max_freq / bar_width); - log_info(" + represents [1,%d) endpoint(s)\n", max_freq / bar_width); - for (unsigned i = 0; i < num_bins; ++i) - log_info("[%6d, %6d) |%s%c\n", min_slack + bin_size * i, min_slack + bin_size * (i + 1), - std::string(bins[i] * bar_width / max_freq, '*').c_str(), - (bins[i] * bar_width) % max_freq > 0 ? '+' : ' '); - } - - // Update timing results in the context - if (update_results) { - auto &results = ctx->timing_result; - - results.clock_fmax = std::move(clock_fmax); - results.clock_paths = std::move(clock_reports); - results.xclock_paths = std::move(xclock_reports); - - results.detailed_net_timings = std::move(detailed_net_timings); - } -} - -NEXTPNR_NAMESPACE_END diff --git a/common/timing.h b/common/timing.h deleted file mode 100644 index fe1bcaa8..00000000 --- a/common/timing.h +++ /dev/null @@ -1,236 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * 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 TIMING_H -#define TIMING_H - -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct CellPortKey -{ - CellPortKey(){}; - CellPortKey(IdString cell, IdString port) : cell(cell), port(port){}; - explicit CellPortKey(const PortRef &pr) - { - NPNR_ASSERT(pr.cell != nullptr); - cell = pr.cell->name; - port = pr.port; - } - IdString cell, port; - unsigned int hash() const { return mkhash(cell.hash(), port.hash()); } - inline bool operator==(const CellPortKey &other) const { return (cell == other.cell) && (port == other.port); } - inline bool operator!=(const CellPortKey &other) const { return (cell != other.cell) || (port != other.port); } - inline bool operator<(const CellPortKey &other) const - { - return cell == other.cell ? port < other.port : cell < other.cell; - } -}; - -struct ClockDomainKey -{ - IdString clock; - ClockEdge edge; - ClockDomainKey(IdString clock_net, ClockEdge edge) : clock(clock_net), edge(edge){}; - // probably also need something here to deal with constraints - inline bool is_async() const { return clock == IdString(); } - - unsigned int hash() const { return mkhash(clock.hash(), int(edge)); } - - inline bool operator==(const ClockDomainKey &other) const { return (clock == other.clock) && (edge == other.edge); } -}; - -typedef int domain_id_t; - -struct ClockDomainPairKey -{ - domain_id_t launch, capture; - ClockDomainPairKey(domain_id_t launch, domain_id_t capture) : launch(launch), capture(capture){}; - inline bool operator==(const ClockDomainPairKey &other) const - { - return (launch == other.launch) && (capture == other.capture); - } - unsigned int hash() const { return mkhash(launch, capture); } -}; - -struct TimingAnalyser -{ - public: - TimingAnalyser(Context *ctx) : ctx(ctx){}; - void setup(); - void run(bool update_route_delays = true); - void print_report(); - - // This is used when routers etc are not actually binding detailed routing (due to congestion or an abstracted - // model), but want to re-run STA with their own calculated delays - void set_route_delay(CellPortKey port, DelayPair value); - - float get_criticality(CellPortKey port) const { return ports.at(port).worst_crit; } - float get_setup_slack(CellPortKey port) const { return ports.at(port).worst_setup_slack; } - float get_domain_setup_slack(CellPortKey port) const - { - delay_t slack = std::numeric_limits::max(); - for (const auto &dp : ports.at(port).domain_pairs) - slack = std::min(slack, domain_pairs.at(dp.first).worst_setup_slack); - return slack; - } - - bool setup_only = false; - bool verbose_mode = false; - bool have_loops = false; - bool updated_domains = false; - - private: - void init_ports(); - void get_cell_delays(); - void get_route_delays(); - void topo_sort(); - void setup_port_domains(); - - void reset_times(); - - void walk_forward(); - void walk_backward(); - - void compute_slack(); - void compute_criticality(); - - void print_fmax(); - // get the N most failing endpoints for a given domain pair - std::vector get_failing_eps(domain_id_t domain_pair, int count); - // print the critical path for an endpoint and domain pair - void print_critical_path(CellPortKey endpoint, domain_id_t domain_pair); - - const DelayPair init_delay{std::numeric_limits::max(), std::numeric_limits::lowest()}; - - // Set arrival/required times if more/less than the current value - void set_arrival_time(CellPortKey target, domain_id_t domain, DelayPair arrival, int path_length, - CellPortKey prev = CellPortKey()); - void set_required_time(CellPortKey target, domain_id_t domain, DelayPair required, int path_length, - CellPortKey prev = CellPortKey()); - - // To avoid storing the domain tag structure (which could get large when considering more complex constrained tag - // cases), assign each domain an ID and use that instead - // An arrival or required time entry. Stores both the min/max delays; and the traversal to reach them for critical - // path reporting - struct ArrivReqTime - { - DelayPair value; - CellPortKey bwd_min, bwd_max; - int path_length; - }; - // Data per port-domain tuple - struct PortDomainPairData - { - delay_t setup_slack = std::numeric_limits::max(), hold_slack = std::numeric_limits::max(); - delay_t budget = std::numeric_limits::max(); - int max_path_length = 0; - float criticality = 0; - }; - - // A cell timing arc, used to cache cell timings and reduce the number of potentially-expensive Arch API calls - struct CellArc - { - - enum ArcType - { - COMBINATIONAL, - SETUP, - HOLD, - CLK_TO_Q - } type; - - IdString other_port; - DelayQuad value; - // Clock polarity, not used for combinational arcs - ClockEdge edge; - - CellArc(ArcType type, IdString other_port, DelayQuad value) - : type(type), other_port(other_port), value(value), edge(RISING_EDGE){}; - CellArc(ArcType type, IdString other_port, DelayQuad value, ClockEdge edge) - : type(type), other_port(other_port), value(value), edge(edge){}; - }; - - // Timing data for every cell port - struct PerPort - { - CellPortKey cell_port; - PortType type; - // per domain timings - dict arrival; - dict required; - dict domain_pairs; - // cell timing arcs to (outputs)/from (inputs) from this port - std::vector cell_arcs; - // routing delay into this port (input ports only) - DelayPair route_delay{0}; - // worst criticality and slack across domain pairs - float worst_crit = 0; - delay_t worst_setup_slack = std::numeric_limits::max(), - worst_hold_slack = std::numeric_limits::max(); - }; - - struct PerDomain - { - PerDomain(ClockDomainKey key) : key(key){}; - ClockDomainKey key; - // these are pairs (signal port; clock port) - std::vector> startpoints, endpoints; - }; - - struct PerDomainPair - { - PerDomainPair(ClockDomainPairKey key) : key(key){}; - ClockDomainPairKey key; - DelayPair period{0}; - delay_t worst_setup_slack, worst_hold_slack; - }; - - CellInfo *cell_info(const CellPortKey &key); - PortInfo &port_info(const CellPortKey &key); - - domain_id_t domain_id(IdString cell, IdString clock_port, ClockEdge edge); - domain_id_t domain_id(const NetInfo *net, ClockEdge edge); - domain_id_t domain_pair_id(domain_id_t launch, domain_id_t capture); - - void copy_domains(const CellPortKey &from, const CellPortKey &to, bool backwards); - - dict ports; - dict domain_to_id; - dict pair_to_id; - std::vector domains; - std::vector domain_pairs; - - std::vector topological_order; - - Context *ctx; -}; - -// Evenly redistribute the total path slack amongst all sinks on each path -void assign_budget(Context *ctx, bool quiet = false); - -// Perform timing analysis and print out the fmax, and optionally the -// critical path -void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false, - bool warn_on_failure = false, bool update_results = false); - -NEXTPNR_NAMESPACE_END - -#endif diff --git a/common/timing_opt.cc b/common/timing_opt.cc deleted file mode 100644 index f9246292..00000000 --- a/common/timing_opt.cc +++ /dev/null @@ -1,561 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * 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. - * - */ - -/* - * Timing-optimised detailed placement algorithm using BFS of the neighbour graph created from cells - * on a critical path - * - * Based on "An Effective Timing-Driven Detailed Placement Algorithm for FPGAs" - * https://www.cerc.utexas.edu/utda/publications/C205.pdf - * - * Modifications made to deal with the smaller Bels that nextpnr uses instead of swapping whole tiles, - * and deal with the fact that not every cell on the crit path may be swappable. - */ - -#include "timing_opt.h" -#include -#include -#include "nextpnr.h" -#include "timing.h" -#include "util.h" - -NEXTPNR_NAMESPACE_BEGIN - -class TimingOptimiser -{ - public: - TimingOptimiser(Context *ctx, TimingOptCfg cfg) : ctx(ctx), cfg(cfg), tmg(ctx){}; - bool optimise() - { - log_info("Running timing-driven placement optimisation...\n"); - ctx->lock(); - if (ctx->verbose) - timing_analysis(ctx, false, true, false, false); - tmg.setup(); - for (int i = 0; i < 30; i++) { - log_info(" Iteration %d...\n", i); - tmg.run(); - setup_delay_limits(); - auto crit_paths = find_crit_paths(0.98, 50000); - for (auto &path : crit_paths) - optimise_path(path); - if (ctx->verbose) - timing_analysis(ctx, false, true, false, false); - } - ctx->unlock(); - return true; - } - - private: - void setup_delay_limits() - { - max_net_delay.clear(); - for (auto &net : ctx->nets) { - NetInfo *ni = net.second.get(); - if (ni->driver.cell == nullptr) - continue; - for (auto usr : ni->users) { - max_net_delay[std::make_pair(usr.cell->name, usr.port)] = std::numeric_limits::max(); - } - for (auto usr : ni->users) { - delay_t net_delay = ctx->getNetinfoRouteDelay(ni, usr); - delay_t slack = tmg.get_setup_slack(CellPortKey(usr)); - delay_t domain_slack = tmg.get_domain_setup_slack(CellPortKey(usr)); - if (slack == std::numeric_limits::max()) - continue; - max_net_delay[std::make_pair(usr.cell->name, usr.port)] = net_delay + ((slack - domain_slack) / 10); - } - } - } - - bool check_cell_delay_limits(CellInfo *cell) - { - for (const auto &port : cell->ports) { - int nc; - if (ctx->getPortTimingClass(cell, port.first, nc) == TMG_IGNORE) - continue; - NetInfo *net = port.second.net; - if (net == nullptr) - continue; - if (port.second.type == PORT_IN) { - if (net->driver.cell == nullptr || net->driver.cell->bel == BelId()) - continue; - for (auto user : net->users) { - if (user.cell == cell && user.port == port.first) { - if (ctx->predictArcDelay(net, user) > - 1.1 * max_net_delay.at(std::make_pair(cell->name, port.first))) - return false; - } - } - - } else if (port.second.type == PORT_OUT) { - for (auto user : net->users) { - // This could get expensive for high-fanout nets?? - BelId dstBel = user.cell->bel; - if (dstBel == BelId()) - continue; - if (ctx->predictArcDelay(net, user) > - 1.1 * max_net_delay.at(std::make_pair(user.cell->name, user.port))) { - - return false; - } - } - } - } - return true; - } - - BelId cell_swap_bel(CellInfo *cell, BelId newBel) - { - BelId oldBel = cell->bel; - if (oldBel == newBel) - return oldBel; - CellInfo *other_cell = ctx->getBoundBelCell(newBel); - NPNR_ASSERT(other_cell == nullptr || other_cell->belStrength <= STRENGTH_WEAK); - ctx->unbindBel(oldBel); - if (other_cell != nullptr) { - ctx->unbindBel(newBel); - ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK); - } - ctx->bindBel(newBel, cell, STRENGTH_WEAK); - return oldBel; - } - - // Check that a series of moves are both legal and remain within maximum delay bounds - // Moves are specified as a vector of pairs - bool acceptable_move(std::vector> &move, bool check_delays = true) - { - for (auto &entry : move) { - if (!ctx->isBelLocationValid(entry.first->bel)) - return false; - if (!ctx->isBelLocationValid(entry.second)) - return false; - if (!check_delays) - continue; - if (!check_cell_delay_limits(entry.first)) - return false; - // We might have swapped another cell onto the original bel. Check this for max delay violations - // too - CellInfo *swapped = ctx->getBoundBelCell(entry.second); - if (swapped != nullptr && !check_cell_delay_limits(swapped)) - return false; - } - return true; - } - - int find_neighbours(CellInfo *cell, IdString prev_cell, int d, bool allow_swap) - { - BelId curr = cell->bel; - Loc curr_loc = ctx->getBelLocation(curr); - int found_count = 0; - cell_neighbour_bels[cell->name] = pool{}; - for (int dy = -d; dy <= d; dy++) { - for (int dx = -d; dx <= d; dx++) { - // Go through all the Bels at this location - // First, find all bels of the correct type that are either unbound or bound normally - // Strongly bound bels are ignored - // FIXME: This means that we cannot touch carry chains or similar relatively constrained macros - std::vector free_bels_at_loc; - std::vector bound_bels_at_loc; - for (auto bel : ctx->getBelsByTile(curr_loc.x + dx, curr_loc.y + dy)) { - if (!ctx->isValidBelForCellType(cell->type, bel)) - continue; - CellInfo *bound = ctx->getBoundBelCell(bel); - if (bound == nullptr) { - free_bels_at_loc.push_back(bel); - } else if (bound->belStrength <= STRENGTH_WEAK && bound->cluster == ClusterId()) { - bound_bels_at_loc.push_back(bel); - } - } - BelId candidate; - - while (!free_bels_at_loc.empty() || !bound_bels_at_loc.empty()) { - BelId try_bel; - if (!free_bels_at_loc.empty()) { - int try_idx = ctx->rng(int(free_bels_at_loc.size())); - try_bel = free_bels_at_loc.at(try_idx); - free_bels_at_loc.erase(free_bels_at_loc.begin() + try_idx); - } else { - int try_idx = ctx->rng(int(bound_bels_at_loc.size())); - try_bel = bound_bels_at_loc.at(try_idx); - bound_bels_at_loc.erase(bound_bels_at_loc.begin() + try_idx); - } - if (bel_candidate_cells.count(try_bel) && !allow_swap) { - // Overlap is only allowed if it is with the previous cell (this is handled by removing those - // edges in the graph), or if allow_swap is true to deal with cases where overlap means few - // neighbours are identified - if (bel_candidate_cells.at(try_bel).size() > 1 || - (bel_candidate_cells.at(try_bel).size() == 1 && - *(bel_candidate_cells.at(try_bel).begin()) != prev_cell)) - continue; - } - // TODO: what else to check here? - candidate = try_bel; - break; - } - - if (candidate != BelId()) { - cell_neighbour_bels[cell->name].insert(candidate); - bel_candidate_cells[candidate].insert(cell->name); - // Work out if we need to delete any overlap - std::vector overlap; - for (auto other : bel_candidate_cells[candidate]) - if (other != cell->name && other != prev_cell) - overlap.push_back(other); - if (overlap.size() > 0) - NPNR_ASSERT(allow_swap); - for (auto ov : overlap) { - bel_candidate_cells[candidate].erase(ov); - cell_neighbour_bels[ov].erase(candidate); - } - } - } - } - return found_count; - } - - std::vector> find_crit_paths(float crit_thresh, size_t max_count) - { - std::vector> crit_paths; - std::vector>> crit_nets; - std::vector netnames; - std::transform(ctx->nets.begin(), ctx->nets.end(), std::back_inserter(netnames), - [](const std::pair> &kv) { return kv.first; }); - ctx->sorted_shuffle(netnames); - for (auto net : netnames) { - if (crit_nets.size() >= max_count) - break; - float highest_crit = 0; - store_index crit_user_idx{}; - NetInfo *ni = ctx->nets.at(net).get(); - for (auto usr : ni->users.enumerate()) { - float crit = tmg.get_criticality(CellPortKey(usr.value)); - if (crit > highest_crit) { - highest_crit = crit; - crit_user_idx = usr.index; - } - } - if (highest_crit > crit_thresh) - crit_nets.emplace_back(ni, crit_user_idx); - } - - pool used_ports; - - for (auto crit_net : crit_nets) { - - if (used_ports.count(&(crit_net.first->users.at(crit_net.second)))) - continue; - - std::deque crit_path; - - // FIXME: This will fail badly on combinational loops - - // Iterate backwards following greatest criticality - NetInfo *back_cursor = crit_net.first; - while (back_cursor != nullptr) { - float max_crit = 0; - std::pair> crit_sink{nullptr, {}}; - CellInfo *cell = back_cursor->driver.cell; - if (cell == nullptr) - break; - for (auto port : cell->ports) { - if (port.second.type != PORT_IN) - continue; - NetInfo *pn = port.second.net; - if (pn == nullptr) - continue; - int ccount; - DelayQuad combDelay; - TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount); - if (tpclass != TMG_COMB_INPUT) - continue; - bool is_path = ctx->getCellDelay(cell, port.first, back_cursor->driver.port, combDelay); - if (!is_path) - continue; - float usr_crit = tmg.get_criticality(CellPortKey(cell->name, port.first)); - if (used_ports.count(&(pn->users.at(port.second.user_idx)))) - continue; - if (usr_crit >= max_crit) { - max_crit = usr_crit; - crit_sink = std::make_pair(pn, port.second.user_idx); - } - } - - if (crit_sink.first != nullptr) { - crit_path.push_front(&(crit_sink.first->users.at(crit_sink.second))); - used_ports.insert(&(crit_sink.first->users.at(crit_sink.second))); - } - back_cursor = crit_sink.first; - } - // Iterate forwards following greatest criticiality - PortRef *fwd_cursor = &(crit_net.first->users.at(crit_net.second)); - while (fwd_cursor != nullptr) { - crit_path.push_back(fwd_cursor); - float max_crit = 0; - std::pair> crit_sink{nullptr, {}}; - CellInfo *cell = fwd_cursor->cell; - for (auto port : cell->ports) { - if (port.second.type != PORT_OUT) - continue; - NetInfo *pn = port.second.net; - if (pn == nullptr) - continue; - - int ccount; - DelayQuad combDelay; - TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount); - if (tpclass != TMG_COMB_OUTPUT && tpclass != TMG_REGISTER_OUTPUT) - continue; - bool is_path = ctx->getCellDelay(cell, fwd_cursor->port, port.first, combDelay); - if (!is_path) - continue; - for (auto usr : pn->users.enumerate()) { - if (used_ports.count(&(pn->users.at(usr.index)))) - continue; - float crit = tmg.get_criticality(CellPortKey(usr.value)); - if (crit >= max_crit) { - max_crit = crit; - crit_sink = std::make_pair(pn, usr.index); - } - } - } - if (crit_sink.first != nullptr) { - fwd_cursor = &(crit_sink.first->users.at(crit_sink.second)); - used_ports.insert(&(crit_sink.first->users.at(crit_sink.second))); - } else { - fwd_cursor = nullptr; - } - } - - std::vector crit_path_vec; - std::copy(crit_path.begin(), crit_path.end(), std::back_inserter(crit_path_vec)); - crit_paths.push_back(crit_path_vec); - } - - return crit_paths; - } - - void optimise_path(std::vector &path) - { - path_cells.clear(); - cell_neighbour_bels.clear(); - bel_candidate_cells.clear(); - if (ctx->debug) - log_info("Optimising the following path: \n"); - - auto front_port = path.front(); - NetInfo *front_net = front_port->cell->ports.at(front_port->port).net; - if (front_net != nullptr && front_net->driver.cell != nullptr) { - auto front_cell = front_net->driver.cell; - if (front_cell->belStrength <= STRENGTH_WEAK && cfg.cellTypes.count(front_cell->type) && - front_cell->cluster == ClusterId()) { - path_cells.push_back(front_cell->name); - } - } - - for (auto port : path) { - if (ctx->debug) { - float crit = tmg.get_criticality(CellPortKey(*port)); - log_info(" %s.%s at %s crit %0.02f\n", port->cell->name.c_str(ctx), port->port.c_str(ctx), - ctx->nameOfBel(port->cell->bel), crit); - } - if (std::find(path_cells.begin(), path_cells.end(), port->cell->name) != path_cells.end()) - continue; - if (port->cell->belStrength > STRENGTH_WEAK || !cfg.cellTypes.count(port->cell->type) || - port->cell->cluster != ClusterId()) - continue; - if (ctx->debug) - log_info(" can move\n"); - path_cells.push_back(port->cell->name); - } - - if (path_cells.size() < 2) { - if (ctx->debug) { - log_info("Too few moveable cells; skipping path\n"); - log_break(); - } - - return; - } - - // Calculate original delay before touching anything - delay_t original_delay = 0; - - for (size_t i = 0; i < path.size(); i++) { - auto &port = path.at(i)->cell->ports.at(path.at(i)->port); - NetInfo *pn = port.net; - if (port.user_idx) - original_delay += ctx->predictArcDelay(pn, pn->users.at(port.user_idx)); - } - - IdString last_cell; - const int d = 2; // FIXME: how to best determine d - for (auto cell : path_cells) { - // FIXME: when should we allow swapping due to a lack of candidates - find_neighbours(ctx->cells[cell].get(), last_cell, d, false); - last_cell = cell; - } - - if (ctx->debug) { - for (auto cell : path_cells) { - log_info("Candidate neighbours for %s (%s):\n", cell.c_str(ctx), ctx->nameOfBel(ctx->cells[cell]->bel)); - for (auto neigh : cell_neighbour_bels.at(cell)) { - log_info(" %s\n", ctx->nameOfBel(neigh)); - } - } - } - - // Actual BFS path optimisation algorithm - dict> cumul_costs; - dict, std::pair> backtrace; - std::queue> visit; - pool> to_visit; - - for (auto startbel : cell_neighbour_bels[path_cells.front()]) { - // Swap for legality check - CellInfo *cell = ctx->cells.at(path_cells.front()).get(); - BelId origBel = cell_swap_bel(cell, startbel); - std::vector> move{std::make_pair(cell, origBel)}; - if (acceptable_move(move)) { - auto entry = std::make_pair(0, startbel); - visit.push(entry); - cumul_costs[path_cells.front()][startbel] = 0; - } - // Swap back - cell_swap_bel(cell, origBel); - } - - while (!visit.empty()) { - auto entry = visit.front(); - visit.pop(); - auto cellname = path_cells.at(entry.first); - if (entry.first == int(path_cells.size()) - 1) - continue; - std::vector> move; - // Apply the entire backtrace for accurate legality and delay checks - // This is probably pretty expensive (but also probably pales in comparison to the number of swaps - // SA will make...) - std::vector> route_to_entry; - auto cursor = std::make_pair(cellname, entry.second); - route_to_entry.push_back(cursor); - while (backtrace.count(cursor)) { - cursor = backtrace.at(cursor); - route_to_entry.push_back(cursor); - } - for (auto rt_entry : boost::adaptors::reverse(route_to_entry)) { - CellInfo *cell = ctx->cells.at(rt_entry.first).get(); - BelId origBel = cell_swap_bel(cell, rt_entry.second); - move.push_back(std::make_pair(cell, origBel)); - } - - // Have a look at where we can travel from here - for (auto neighbour : cell_neighbour_bels.at(path_cells.at(entry.first + 1))) { - // Edges between overlapping bels are deleted - if (neighbour == entry.second) - continue; - // Experimentally swap the next path cell onto the neighbour bel we are trying - IdString ncname = path_cells.at(entry.first + 1); - CellInfo *next_cell = ctx->cells.at(ncname).get(); - BelId origBel = cell_swap_bel(next_cell, neighbour); - move.push_back(std::make_pair(next_cell, origBel)); - - delay_t total_delay = 0; - - for (size_t i = 0; i < path.size(); i++) { - auto &port = path.at(i)->cell->ports.at(path.at(i)->port); - NetInfo *pn = port.net; - if (port.user_idx) - total_delay += ctx->predictArcDelay(pn, pn->users.at(port.user_idx)); - if (path.at(i)->cell == next_cell) - break; - } - - // First, check if the move is actually worthwhile from a delay point of view before the expensive - // legality check - if (!cumul_costs.count(ncname) || !cumul_costs.at(ncname).count(neighbour) || - cumul_costs.at(ncname).at(neighbour) > total_delay) { - // Now check that the swaps we have made to get here are legal and meet max delay requirements - if (acceptable_move(move)) { - cumul_costs[ncname][neighbour] = total_delay; - backtrace[std::make_pair(ncname, neighbour)] = std::make_pair(cellname, entry.second); - if (!to_visit.count(std::make_pair(entry.first + 1, neighbour))) - visit.push(std::make_pair(entry.first + 1, neighbour)); - } - } - // Revert the experimental swap - cell_swap_bel(move.back().first, move.back().second); - move.pop_back(); - } - - // Revert move by swapping cells back to their original order - // Execute swaps in reverse order to how we made them originally - for (auto move_entry : boost::adaptors::reverse(move)) { - cell_swap_bel(move_entry.first, move_entry.second); - } - } - - // Did we find a solution?? - if (cumul_costs.count(path_cells.back())) { - // Find the end position with the lowest total delay - auto &end_options = cumul_costs.at(path_cells.back()); - auto lowest = std::min_element(end_options.begin(), end_options.end(), - [](const std::pair &a, const std::pair &b) { - return a.second < b.second; - }); - NPNR_ASSERT(lowest != end_options.end()); - - std::vector> route_to_solution; - auto cursor = std::make_pair(path_cells.back(), lowest->first); - route_to_solution.push_back(cursor); - while (backtrace.count(cursor)) { - cursor = backtrace.at(cursor); - route_to_solution.push_back(cursor); - } - if (ctx->debug) - log_info("Found a solution with cost %.02f ns (existing path %.02f ns)\n", - ctx->getDelayNS(lowest->second), ctx->getDelayNS(original_delay)); - for (auto rt_entry : boost::adaptors::reverse(route_to_solution)) { - CellInfo *cell = ctx->cells.at(rt_entry.first).get(); - cell_swap_bel(cell, rt_entry.second); - if (ctx->debug) - log_info(" %s at %s\n", rt_entry.first.c_str(ctx), ctx->nameOfBel(rt_entry.second)); - } - - } else { - if (ctx->debug) - log_info("Solution was not found\n"); - } - if (ctx->debug) - log_break(); - } - - // Current candidate Bels for cells (linked in both direction> - std::vector path_cells; - dict> cell_neighbour_bels; - dict> bel_candidate_cells; - // Map cell ports to net delay limit - dict, delay_t> max_net_delay; - Context *ctx; - TimingOptCfg cfg; - TimingAnalyser tmg; -}; - -bool timing_opt(Context *ctx, TimingOptCfg cfg) { return TimingOptimiser(ctx, cfg).optimise(); } - -NEXTPNR_NAMESPACE_END diff --git a/common/timing_opt.h b/common/timing_opt.h deleted file mode 100644 index 8f8bc709..00000000 --- a/common/timing_opt.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * 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. - * - */ - -#include "log.h" -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -struct TimingOptCfg -{ - TimingOptCfg(Context *ctx) {} - - // The timing optimiser will *only* optimise cells of these types - // Normally these would only be logic cells (or tiles if applicable), the algorithm makes little sense - // for other cell types - pool cellTypes; -}; - -extern bool timing_opt(Context *ctx, TimingOptCfg cfg); - -NEXTPNR_NAMESPACE_END diff --git a/common/util.h b/common/util.h deleted file mode 100644 index c10abb72..00000000 --- a/common/util.h +++ /dev/null @@ -1,241 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * 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 UTIL_H -#define UTIL_H - -#include -#include -#include -#include "nextpnr.h" - -#include "log.h" - -NEXTPNR_NAMESPACE_BEGIN - -// Get a value from a map-style container, returning default if value is not -// found -template -ValueType get_or_default(const Container &ct, const KeyType &key, ValueType def = ValueType()) -{ - auto found = ct.find(key); - if (found == ct.end()) - return def; - else - return found->second; -}; - -// Get a value from a map-style container, returning default if value is not -// found (forces string) -template -std::string str_or_default(const Container &ct, const KeyType &key, std::string def = "") -{ - auto found = ct.find(key); - if (found == ct.end()) - return def; - else { - return found->second; - } -}; - -template -std::string str_or_default(const dict &ct, const KeyType &key, std::string def = "") -{ - auto found = ct.find(key); - if (found == ct.end()) - return def; - else { - if (!found->second.is_string) - log_error("Expecting string value but got integer %d.\n", int(found->second.intval)); - return found->second.as_string(); - } -}; - -// Get a value from a map-style container, converting to int, and returning -// default if value is not found -template int int_or_default(const Container &ct, const KeyType &key, int def = 0) -{ - auto found = ct.find(key); - if (found == ct.end()) - return def; - else - return std::stoi(found->second); -}; - -template int int_or_default(const dict &ct, const KeyType &key, int def = 0) -{ - auto found = ct.find(key); - if (found == ct.end()) - return def; - else { - if (found->second.is_string) { - try { - return std::stoi(found->second.as_string()); - } catch (std::invalid_argument &e) { - log_error("Expecting numeric value but got '%s'.\n", found->second.as_string().c_str()); - } - } else - return found->second.as_int64(); - } -}; - -// As above, but convert to bool -template -bool bool_or_default(const Container &ct, const KeyType &key, bool def = false) -{ - return bool(int_or_default(ct, key, int(def))); -}; - -// Return a net if port exists, or nullptr -inline const NetInfo *get_net_or_empty(const CellInfo *cell, const IdString port) -{ - auto found = cell->ports.find(port); - if (found != cell->ports.end()) - return found->second.net; - else - return nullptr; -} - -inline NetInfo *get_net_or_empty(CellInfo *cell, const IdString port) -{ - auto found = cell->ports.find(port); - if (found != cell->ports.end()) - return found->second.net; - else - return nullptr; -} - -// Get only value from a forward iterator begin/end pair. -// -// Generates assertion failure if std::distance(begin, end) != 1. -template -inline const typename ForwardIterator::reference get_only_value(ForwardIterator begin, ForwardIterator end) -{ - NPNR_ASSERT(begin != end); - const typename ForwardIterator::reference ret = *begin; - ++begin; - NPNR_ASSERT(begin == end); - return ret; -} - -// Get only value from a forward iterator range pair. -// -// Generates assertion failure if std::distance(r.begin(), r.end()) != 1. -template inline auto get_only_value(ForwardRange r) -{ - auto b = r.begin(); - auto e = r.end(); - return get_only_value(b, e); -} - -// From Yosys -// https://github.com/YosysHQ/yosys/blob/0fb4224ebca86156a1296b9210116d9a9cbebeed/kernel/utils.h#L131 -template > struct TopoSort -{ - bool analyze_loops, found_loops; - std::map, C> database; - std::set> loops; - std::vector sorted; - - TopoSort() - { - analyze_loops = true; - found_loops = false; - } - - void node(T n) - { - if (database.count(n) == 0) - database[n] = std::set(); - } - - void edge(T left, T right) - { - node(left); - database[right].insert(left); - } - - void sort_worker(const T &n, std::set &marked_cells, std::set &active_cells, - std::vector &active_stack) - { - if (active_cells.count(n)) { - found_loops = true; - if (analyze_loops) { - std::set loop; - for (int i = int(active_stack.size()) - 1; i >= 0; i--) { - loop.insert(active_stack[i]); - if (active_stack[i] == n) - break; - } - loops.insert(loop); - } - return; - } - - if (marked_cells.count(n)) - return; - - if (!database.at(n).empty()) { - if (analyze_loops) - active_stack.push_back(n); - active_cells.insert(n); - - for (auto &left_n : database.at(n)) - sort_worker(left_n, marked_cells, active_cells, active_stack); - - if (analyze_loops) - active_stack.pop_back(); - active_cells.erase(n); - } - - marked_cells.insert(n); - sorted.push_back(n); - } - - bool sort() - { - loops.clear(); - sorted.clear(); - found_loops = false; - - std::set marked_cells; - std::set active_cells; - std::vector active_stack; - - for (auto &it : database) - sort_worker(it.first, marked_cells, active_cells, active_stack); - - NPNR_ASSERT(sorted.size() == database.size()); - return !found_loops; - } -}; - -template struct reversed_range_t -{ - T &obj; - explicit reversed_range_t(T &obj) : obj(obj){}; - auto begin() { return obj.rbegin(); } - auto end() { return obj.rend(); } -}; - -template reversed_range_t reversed_range(T &obj) { return reversed_range_t(obj); } - -NEXTPNR_NAMESPACE_END - -#endif -- cgit v1.2.3