/* * 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 SITE_ARCH_H #define SITE_ARCH_H #include #include #include "PhysicalNetlist.capnp.h" #include "arch_iterators.h" #include "chipdb.h" #include "hashlib.h" #include "log.h" #include "nextpnr_namespaces.h" #include "nextpnr_types.h" NEXTPNR_NAMESPACE_BEGIN struct Context; struct SiteInformation { const Context *ctx; const int32_t tile; const int32_t tile_type; const int32_t site; const pool &cells_in_site; SiteInformation(const Context *ctx, int32_t tile, int32_t site, const pool &cells_in_site); inline const ChipInfoPOD &chip_info() const NPNR_ALWAYS_INLINE; inline bool is_wire_in_site(WireId wire) const NPNR_ALWAYS_INLINE; inline bool is_bel_in_site(BelId bel) const NPNR_ALWAYS_INLINE; inline bool is_pip_part_of_site(PipId pip) const NPNR_ALWAYS_INLINE; inline bool is_site_port(PipId pip) const NPNR_ALWAYS_INLINE; }; // Site routing needs a modification of the routing graph. Within the site, // the arch can be consulted for edges. However the rest of the routing graph // needs to be reduced for analysis purposes. Wires within the site are // SITE_WIRE's. 4 additional nodes are introduced to model out of site // routing: // - OUT_OF_SITE_SOURCE / OUT_OF_SITE_SINK // - These represent net sources and sinks that are only reachable via the // routing graph (e.g. outside of the site). // - SITE_PORT_SOURCE / SITE_PORT_SINK // - These represent the routing resources connected to other side of site // ports. // // The non-site wire graph is connected like: // // ┌─────────────────┐ ┌────────────────────┐ // │ │ │ │ // │ OUT_OF_SITE_SRC │ │ OUT_OF_SITE_SINK │◄────┐ // │ │ │ │ │ // └┬────────────────┘ └────────────────────┘ │ // │ │ // │ ┌─────────────────────────────────────────────────────┤ // │ │ │ // │ │ │ // │ │ │ // │ │ │ // │ ▼ │ // │ ┌─────────────────┐ ┌─────────────┐ ┌────────────────┐ │ // │ │ │ │ │ │ │ │ // └─────►│ SITE_PORT_SRC ├──►│ Site ├──────►│ SITE_PORT_SINK ├──┘ // │ │ │ │ │ │ // └─────────────────┘ └─────────────┘ └────────────────┘ // struct SiteWire { enum Type { // This wire is just a plain site wire. SITE_WIRE = 0, // This wire is a source that is from outside of the site. OUT_OF_SITE_SOURCE = 1, // This wire is a sink that is from outside of the site. OUT_OF_SITE_SINK = 2, // This wire is the routing graph wire on the dst side of a site port. SITE_PORT_SINK = 3, // This wire is the routing graph wire on the src side of a site port. SITE_PORT_SOURCE = 4, NUMBER_SITE_WIRE_TYPES = 5, }; static inline SiteWire make(const SiteInformation *site_info, WireId site_wire) NPNR_ALWAYS_INLINE; static SiteWire make(const SiteInformation *site_info, PortType port_type, NetInfo *net) NPNR_ALWAYS_INLINE { SiteWire out; if (port_type == PORT_OUT) { out.type = OUT_OF_SITE_SOURCE; out.net = net; } else { out.type = OUT_OF_SITE_SINK; out.net = net; } return out; } static inline SiteWire make_site_port(const SiteInformation *site_info, PipId pip, bool dst_wire); bool operator==(const SiteWire &other) const { return wire == other.wire && type == other.type && pip == other.pip && net == other.net; } bool operator!=(const SiteWire &other) const { return wire != other.wire || type != other.type || pip != other.pip || net != other.net; } bool operator<(const SiteWire &other) const { return std::make_tuple(type, wire, pip, net) < std::make_tuple(other.type, other.wire, other.pip, other.net); } Type type = NUMBER_SITE_WIRE_TYPES; WireId wire; PipId pip; NetInfo *net = nullptr; unsigned int hash() const { return mkhash(mkhash(int(type), wire.hash()), mkhash(pip.hash(), uintptr_t(net))); } }; struct SitePip { enum Type { // This is a plain regular site pip. SITE_PIP = 0, // This pip is a site port, and connects a SITE_WIRE to a SITE_PORT_SINK/SITE_PORT_SRC SITE_PORT = 1, // This pip connects a OUT_OF_SITE_SOURCE to a SITE_PORT_SRC SOURCE_TO_SITE_PORT = 2, // This pip connects a SITE_PORT_SINK to a OUT_OF_SITE_SINK SITE_PORT_TO_SINK = 3, // This pip connects a SITE_PORT_SINK to a SITE_PORT_SRC. SITE_PORT_TO_SITE_PORT = 4, INVALID_TYPE = 5, }; static inline SitePip make(const SiteInformation *site_info, PipId pip); static SitePip make(const SiteInformation *site_info, SiteWire src, PipId dst) { NPNR_ASSERT(src.type == SiteWire::OUT_OF_SITE_SOURCE); SitePip out; out.type = SOURCE_TO_SITE_PORT; out.pip = dst; out.wire = src; return out; } static SitePip make(const SiteInformation *site_info, PipId src, SiteWire dst) { NPNR_ASSERT(dst.type == SiteWire::OUT_OF_SITE_SINK); SitePip out; out.type = SITE_PORT_TO_SINK; out.pip = src; out.wire = dst; return out; } static SitePip make(const SiteInformation *site_info, PipId src_pip, PipId dst_pip) { SitePip out; out.type = SITE_PORT_TO_SITE_PORT; out.pip = src_pip; out.other_pip = dst_pip; return out; } Type type = INVALID_TYPE; // For SITE_PORT_TO_SITE_PORT connections, pip is the site -> routing pip. PipId pip; SiteWire wire; // For SITE_PORT_TO_SITE_PORT connections, other_pip is the routing -> // site pip. PipId other_pip; bool operator==(const SitePip &other) const { return type == other.type && pip == other.pip && wire == other.wire && other_pip == other.other_pip; } bool operator!=(const SitePip &other) const { return type != other.type || pip != other.pip || wire != other.wire || other_pip != other.other_pip; } unsigned int hash() const { return mkhash(mkhash(int(type), pip.hash()), mkhash(wire.hash(), other_pip.hash())); } }; struct SitePipDownhillRange; struct SitePipUphillRange; struct SiteWireRange; struct SiteNetInfo; struct SitePipMap { SitePip pip; size_t count; }; struct SiteNetMap { SiteNetInfo *net; size_t count; }; struct SiteNetInfo { NetInfo *net; SiteWire driver; pool users; dict wires; }; struct SiteArch { const Context *const ctx; const SiteInformation *const site_info; dict nets; dict wire_to_nets; NetInfo blocking_net; SiteNetInfo blocking_site_net; std::vector input_site_ports; std::vector output_site_ports; std::vector out_of_site_sources; std::vector out_of_site_sinks; // A site port that is present in this dictionary is blocked for all those nets except any in the attached pool dict> blocked_site_ports; SiteArch(const SiteInformation *site_info); inline SiteWire getPipSrcWire(const SitePip &site_pip) const NPNR_ALWAYS_INLINE; inline SiteWire getPipDstWire(const SitePip &site_pip) const NPNR_ALWAYS_INLINE; // Does this site pip always invert its signal? inline bool isInverting(const SitePip &site_pip) const NPNR_ALWAYS_INLINE; // Can this site pip optional invert its signal? inline bool canInvert(const SitePip &site_pip) const NPNR_ALWAYS_INLINE; // For a site port, returns the preferred constant net type. // // If no preference, then NetType is SIGNAL. inline PhysicalNetlist::PhysNetlist::NetType prefered_constant_net_type(const SitePip &site_pip) const; inline SitePipDownhillRange getPipsDownhill(const SiteWire &site_wire) const NPNR_ALWAYS_INLINE; inline SitePipUphillRange getPipsUphill(const SiteWire &site_wire) const NPNR_ALWAYS_INLINE; SiteWireRange getWires() const; inline SiteWire getBelPinWire(BelId bel, IdString pin) const NPNR_ALWAYS_INLINE; inline PortType getBelPinType(BelId bel, IdString pin) const NPNR_ALWAYS_INLINE; const char *nameOfWire(const SiteWire &wire) const; const char *nameOfPip(const SitePip &pip) const; const char *nameOfNet(const SiteNetInfo *net) const; bool debug() const; bool bindWire(const SiteWire &wire, SiteNetInfo *net) { auto result = wire_to_nets.emplace(wire, SiteNetMap{net, 1}); if (result.first->second.net != net) { if (debug()) { log_info("Net conflict binding wire %s to net %s, conflicts with net %s\n", nameOfWire(wire), nameOfNet(net), nameOfNet(result.first->second.net)); } return false; } if (!result.second) { result.first->second.count += 1; } return true; } SiteNetInfo *unbindWire(const SiteWire &wire) { auto iter = wire_to_nets.find(wire); NPNR_ASSERT(iter != wire_to_nets.end()); NPNR_ASSERT(iter->second.count >= 1); SiteNetInfo *net = iter->second.net; iter->second.count -= 1; if (iter->second.count == 0) { wire_to_nets.erase(iter); } return net; } bool bindPip(const SitePip &pip, SiteNetInfo *net); void unbindPip(const SitePip &pip); void archcheck(); bool is_pip_synthetic(const SitePip &pip) const NPNR_ALWAYS_INLINE; SyntheticType pip_synthet
import pytest

import env  # noqa: F401
from pybind11_tests import local_bindings as m


def test_load_external():
    """Load a `py::module_local` type that's only registered in an external module"""
    import pybind11_cross_module_tests as cm

    assert m.load_external1(cm.ExternalType1(11)) == 11
    assert m.load_external2(cm.ExternalType2(22)) == 22

    with pytest.raises(TypeError) as excinfo:
        assert m.load_external2(cm.ExternalType1(21)) == 21
    assert "incompatible function arguments" in str(excinfo.value)

    with pytest.raises(TypeError) as excinfo:
        assert m.load_external1(cm.ExternalType2(12)) == 12
    assert "incompatible function arguments" in str(excinfo.value)


def test_local_bindings():
    """Tests that duplicate `py::module_local` class bindings work across modules"""

    # Make sure we can load the second module with the conflicting (but local) definition:
    import pybind11_cross_module_tests as cm

    i1 = m.LocalType(5)
    assert i1.get() == 4
    assert i1.get3() == 8

    i2 = cm.LocalType(10)
    assert i2.get() == 11
    assert i2.get2() == 12

    assert not hasattr(i1, "get2")
    assert not hasattr(i2, "get3")

    # Loading within the local module
    assert m.local_value(i1) == 5
    assert cm.local_value(i2) == 10

    # Cross-module loading works as well (on failure, the type loader looks for
    # external module-local converters):
    assert m.local_value(i2) == 10
    assert cm.local_value(i1) == 5


def test_nonlocal_failure():
    """Tests that attempting to register a non-local type in multiple modules fails"""
    import pybind11_cross_module_tests as cm

    with pytest.raises(RuntimeError) as excinfo:
        cm.register_nonlocal()
    assert (
        str(excinfo.value) == 'generic_type: type "NonLocalType" is already registered!'
    )


def test_duplicate_local():
    """Tests expected failure when registering a class twice with py::local in the same module"""
    with pytest.raises(RuntimeError) as excinfo:
        m.register_local_external()
    import pybind11_tests

    assert str(excinfo.value) == (
        'generic_type: type "LocalExternal" is already registered!'
        if hasattr(pybind11_tests, "class_")
        else "test_class not enabled"
    )


def test_stl_bind_local():
    import pybind11_cross_module_tests as cm

    v1, v2 = m.LocalVec(), cm.LocalVec()
    v1.append(m.LocalType(1))
    v1.append(m.LocalType(2))
    v2.append(cm.LocalType(1))
    v2.append(cm.LocalType(2))

    # Cross module value loading:
    v1.append(cm.LocalType(3))
    v2.append(m.LocalType(3))

    assert [i.get() for i in v1] == [0, 1, 2]
    assert [i.get() for i in v2] == [2, 3, 4]

    v3, v4 = m.NonLocalVec(), cm.NonLocalVec2()
    v3.append(m.NonLocalType(1))
    v3.append(m.NonLocalType(2))
    v4.append(m.NonLocal2(3))
    v4.append(m.NonLocal2(4))

    assert [i.get() for i in v3] == [1, 2]
    assert [i.get() for i in v4] == [13, 14]

    d1, d2 = m.LocalMap(), cm.LocalMap()
    d1["a"] = v1[0]
    d1["b"] = v1[1]
    d2["c"] = v2[0]
    d2["d"] = v2[1]
    assert {i: d1[i].get() for i in d1} == {"a": 0, "b": 1}
    assert {i: d2[i].get() for i in d2} == {"c": 2, "d": 3}


def test_stl_bind_global():
    import pybind11_cross_module_tests as cm

    with pytest.raises(RuntimeError) as excinfo:
        cm.register_nonlocal_map()
    assert (
        str(excinfo.value) == 'generic_type: type "NonLocalMap" is already registered!'
    )

    with pytest.raises(RuntimeError) as excinfo:
        cm.register_nonlocal_vec()
    assert (
        str(excinfo.value) == 'generic_type: type "NonLocalVec" is already registered!'
    )

    with pytest.raises(RuntimeError) as excinfo:
        cm.register_nonlocal_map2()
    assert (
        str(excinfo.value) == 'generic_type: type "NonLocalMap2" is already registered!'
    )


def test_mixed_local_global():
    """Local types take precedence over globally registered types: a module with a `module_local`
    type can be registered even if the type is already registered globally.  With the module,
    casting will go to the local type; outside the module casting goes to the global type."""
    import pybind11_cross_module_tests as cm

    m.register_mixed_global()
    m.register_mixed_local()

    a = []
    a.append(m.MixedGlobalLocal(1))
    a.append(m.MixedLocalGlobal(2))
    a.append(m.get_mixed_gl(3))
    a.append(m.get_mixed_lg(4))

    assert [x.get() for x in a] == [101, 1002, 103, 1004]

    cm.register_mixed_global_local()
    cm.register_mixed_local_global()
    a.append(m.MixedGlobalLocal(5))
    a.append(m.MixedLocalGlobal(6))
    a.append(cm.MixedGlobalLocal(7))
    a.append(cm.MixedLocalGlobal(8))
    a.append(m.get_mixed_gl(9))
    a.append(m.get_mixed_lg(10))
    a.append(cm.get_mixed_gl(11))
    a.append(cm.get_mixed_lg(12))

    assert [x.get() for x in a] == [
        101,
        1002,
        103,
        1004,
        105,
        1006,
        207,
        2008,
        109,
        1010,
        211,
        2012,
    ]


def test_internal_locals_differ():
    """Makes sure the internal local type map differs across the two modules"""
    import pybind11_cross_module_tests as cm

    assert m.local_cpp_types_addr() != cm.local_cpp_types_addr()


@pytest.mark.xfail("env.PYPY and sys.pypy_version_info < (7, 3, 2)")
def test_stl_caster_vs_stl_bind(msg):
    """One module uses a generic vector caster from `<pybind11/stl.h>` while the other
    exports `std::vector<int>` via `py:bind_vector` and `py::module_local`"""
    import pybind11_cross_module_tests as cm

    v1 = cm.VectorInt([1, 2, 3])
    assert m.load_vector_via_caster(v1) == 6
    assert cm.load_vector_via_binding(v1) == 6

    v2 = [1, 2, 3]
    assert m.load_vector_via_caster(v2) == 6
    with pytest.raises(TypeError) as excinfo:
        cm.load_vector_via_binding(v2)
    assert (
        msg(excinfo.value)
        == """
    load_vector_via_binding(): incompatible function arguments. The following argument types are supported:
        1. (arg0: pybind11_cross_module_tests.VectorInt) -> int

    Invoked with: [1, 2, 3]
    """
    )


def test_cross_module_calls():
    import pybind11_cross_module_tests as cm

    v1 = m.LocalVec()
    v1.append(m.LocalType(1))
    v2 = cm.LocalVec()
    v2.append(cm.LocalType(2))

    # Returning the self pointer should get picked up as returning an existing
    # instance (even when that instance is of a foreign, non-local type).
    assert m.return_self(v1) is v1
    assert cm.return_self(v2) is v2
    assert m.return_self(v2) is v2
    assert cm.return_self(v1) is v1

    assert m.LocalVec is not cm.LocalVec
    # Returning a copy, on the other hand, always goes to the local type,
    # regardless of where the source type came from.
    assert type(m.return_copy(v1)) is m.LocalVec
    assert type(m.return_copy(v2)) is m.LocalVec
    assert type(cm.return_copy(v1)) is cm.LocalVec
    assert type(cm.return_copy(v2)) is cm.LocalVec

    # Test the example given in the documentation (which also tests inheritance casting):
    mycat = m.Cat("Fluffy")
    mydog = cm.Dog("Rover")
    assert mycat.get_name() == "Fluffy"
    assert mydog.name() == "Rover"
    assert m.Cat.__base__.__name__ == "Pet"
    assert cm.Dog.__base__.__name__ == "Pet"
    assert m.Cat.__base__ is not cm.Dog.__base__
    assert m.pet_name(mycat) == "Fluffy"
    assert m.pet_name(mydog) == "Rover"
    assert cm.pet_name(mycat) == "Fluffy"
    assert cm.pet_name(mydog) == "Rover"

    assert m.MixGL is not cm.MixGL
    a = m.MixGL(1)
    b = cm.MixGL(2)
    assert m.get_gl_value(a) == 11
    assert m.get_gl_value(b) == 12
    assert cm.get_gl_value(a) == 101
    assert cm.get_gl_value(b) == 102

    c, d = m.MixGL2(3), cm.MixGL2(4)
    with pytest.raises(TypeError) as excinfo:
        m.get_gl_value(c)
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
        m.get_gl_value(d)
    assert "incompatible function arguments" in str(excinfo.value)