#include #include #include #include #include #include using testing::_; // -- STRONG TYPES ------------------------------------------------------------- namespace detail { template struct [[nodiscard]] phantom { explicit constexpr phantom(T val) : val{val} {} constexpr T cast() const { return val; } private: T val; }; namespace tag { struct addr; struct len; struct lock; } // namespace tag } // namespace detail using addr_t = detail::phantom; using len_t = detail::phantom; using lock_t = detail::phantom; // -- TLM TARGET MOCK ---------------------------------------------------------- struct target : public sc_core::sc_module { explicit target(sc_core::sc_module_name nm) : sc_module(std::move(nm)) { p_sock.register_b_transport(this, &target::b_transport_int); p_sock.register_get_direct_mem_ptr(this, &target::get_direct_mem_ptr); } tlm_utils::simple_target_socket p_sock{"sock"}; MOCK_METHOD(void, b_transport, (tlm::tlm_generic_payload&, sc_core::sc_time&)); MOCK_METHOD(bool, get_direct_mem_ptr, (tlm::tlm_generic_payload&, tlm::tlm_dmi&)); void inv_dmi(sc_dt::uint64 start, sc_dt::uint64 end) { p_sock->invalidate_direct_mem_ptr(start, end); } private: void b_transport_int(tlm::tlm_generic_payload& tx, sc_core::sc_time& t) { // Set a default response status, mocked method can overwrite it. tx.set_response_status(tlm::TLM_OK_RESPONSE); b_transport(tx, t); } }; // -- TLM INITIATOR UTIL ------------------------------------------------------- struct initiator : public sc_core::sc_module { explicit initiator(sc_core::sc_module_name nm) : sc_module(std::move(nm)) { p_sock.register_invalidate_direct_mem_ptr( this, &initiator::invalidate_direct_mem_ptr); } tlm_utils::simple_initiator_socket p_sock{"sock"}; MOCK_METHOD(void, invalidate_direct_mem_ptr, (sc_dt::uint64, sc_dt::uint64)); bool transport(addr_t a, len_t l, lock_t lk = lock_t{false}) { tlm::tlm_generic_payload tx; setup_tx(tx, a, l); // Set bus lock extension (will be owned by gp). auto* bl = new bus_lock; bl->is_lock = lk.cast(); tx.set_extension(bl); sc_core::sc_time t; p_sock->b_transport(tx, t); // Check bus restored correct original address. auto is_original_addr = [&](auto addr) { return addr == a.cast(); }; EXPECT_PRED1(is_original_addr, tx.get_address()); return tx.is_response_ok(); } struct dmi_result { bool ok; range addr; explicit constexpr operator bool() const { return ok; } }; dmi_result get_dmi(addr_t a, len_t l) { tlm::tlm_generic_payload tx; setup_tx(tx, a, l); tlm::tlm_dmi dmi; const bool ok = p_sock->get_direct_mem_ptr(tx, dmi); // Check bus restored correct original address. auto is_original_addr = [&](auto addr) { return addr == a.cast(); }; EXPECT_PRED1(is_original_addr, tx.get_address()); return {ok, range{dmi.get_start_address(), dmi.get_end_address()}}; } private: void setup_tx(tlm::tlm_generic_payload& tx, addr_t a, len_t l) const { tx.set_command(tlm::TLM_WRITE_COMMAND); tx.set_address(a.cast()); // NOTE: Since the tests dont require data, we set a nullptr (invalid gp, // but okay here). tx.set_data_ptr(nullptr); tx.set_data_length(l.cast()); tx.set_byte_enable_ptr(nullptr); tx.set_byte_enable_length(0); tx.set_response_status(tlm::TLM_INCOMPLETE_RESPONSE); tx.set_dmi_allowed(false); } }; // -- TEST UTILS --------------------------------------------------------------- static std::string tx_to_str(const tlm::tlm_generic_payload& tx) { std::ostringstream oss; // Only print values we are interested in in the tests. oss << "tx.addr=0x" << std::hex << tx.get_address() << std::dec; oss << ",tx.len=" << tx.get_data_length(); if (const bus_lock* ext = tx.get_extension()) { oss << ",tx.is_lock=" << ext->is_lock; } return oss.str(); }; // Custom generic payload (gp) address and length matcher. MATCHER_P2(gp_addr_len, /* arg0 */ arg_addr, /* arg1 */ arg_len, /* desc */ [](addr_t a, len_t l) { tlm::tlm_generic_payload tx; tx.set_address(a.cast()); tx.set_data_length(l.cast()); return tx_to_str(tx); }(arg_addr, arg_len /* ign negation arg */)) { return arg.get_address() == arg_addr.cast() && arg.get_data_length() == arg_len.cast(); } // Custom generic payload (gp) address, length and lock matcher. MATCHER_P3(gp_addr_len_lock, /* arg0 */ arg_addr, /* arg1 */ arg_len, /* arg2 */ arg_lock, /* desc */ [](addr_t a, len_t l, lock_t lk) { tlm::tlm_generic_payload tx; tx.set_address(a.cast()); tx.set_data_length(l.cast()); auto* ext = new bus_lock; ext->is_lock = lk.cast(); tx.set_extension(ext); return tx_to_str(tx); }(arg_addr, arg_len, arg_lock /* ign negation arg */)) { const auto* ext = arg.template get_extension(); return arg.get_address() == arg_addr.cast() && arg.get_data_length() == arg_len.cast() && ext && ext->is_lock == arg_lock.cast(); } // Define operator<< for tlm generic payloads in the tlm namespace. Used // by gtest/gmock when printing payload objects. // It is not a good practice to add functions to non-owned namespaced but for // this test this is okay, just close the eyes :^) namespace tlm { std::ostream& operator<<(std::ostream& os, const tlm::tlm_generic_payload& tx) { return os << tx_to_str(tx); } } // namespace tlm // -- TESTS -------------------------------------------------------------------- struct bus_test : testing::Test { void SetUp() override { bus.attach_initiator(i1.p_sock); bus.attach_initiator(i2.p_sock); } testing::StrictMock t1{"target1"}; testing::StrictMock t2{"target2"}; initiator i1{"initiator1"}; initiator i2{"initiator2"}; lt_bus bus{"bus-testling"}; }; TEST_F(bus_test, zero_based_mapping) { bus.attach_target(t1.p_sock, 0, 0xff); // Enforce EXPECT_CALLs in sequence. testing::InSequence seq; // Relative addressing (zero mapped: relative -> absolute). EXPECT_CALL(t1, b_transport(gp_addr_len(addr_t(0x0), len_t(16)), _)); ASSERT_TRUE(i1.transport(addr_t(0x0), len_t(16))); EXPECT_CALL(t1, b_transport(gp_addr_len(addr_t(0x10), len_t(4)), _)); ASSERT_TRUE(i1.transport(addr_t(0x10), len_t(4))); EXPECT_CALL(t1, b_transport(gp_addr_len(addr_t(0xff), len_t(1)), _)); ASSERT_TRUE(i1.transport(addr_t(0xff), len_t(1))); // Full mapped range. EXPECT_CALL(t1, b_transport(gp_addr_len(addr_t(0x0), len_t(0x100)), _)); ASSERT_TRUE(i1.transport(addr_t(0x0), len_t(0x100))); // Out of bound. ASSERT_FALSE(i1.transport(addr_t(0x0), len_t(0x101))); // oob ASSERT_FALSE(i1.transport(addr_t(0xff), len_t(2))); // oob ASSERT_FALSE(i1.transport(addr_t(0x100), len_t(1))); // oob ASSERT_FALSE(i1.transport(addr_t(0x800), len_t(4))); // oob } TEST_F(bus_test, non_zero_based_mapping) { bus.attach_target(t1.p_sock, 0x100, 0x1ff); // Enforce EXPECT_CALLs in sequence. testing::InSequence seq; // Check relative addressing. EXPECT_CALL(t1, b_transport(gp_addr_len(addr_t(0x0), len_t(16)), _)); ASSERT_TRUE(i1.transport(addr_t(0x100), len_t(16))); EXPECT_CALL(t1, b_transport(gp_addr_len(addr_t(0x10), len_t(4)), _)); ASSERT_TRUE(i1.transport(addr_t(0x110), len_t(4))); EXPECT_CALL(t1, b_transport(gp_addr_len(addr_t(0xff), len_t(1)), _)); ASSERT_TRUE(i1.transport(addr_t(0x1ff), len_t(1))); // Full mapped range. EXPECT_CALL(t1, b_transport(gp_addr_len(addr_t(0x0), len_t(0x100)), _)); ASSERT_TRUE(i1.transport(addr_t(0x100), len_t(0x100))); // Out of bound. ASSERT_FALSE(i1.transport(addr_t(0x0), len_t(4))); // oob ASSERT_FALSE(i1.transport(addr_t(0x100), len_t(0x101))); // oob ASSERT_FALSE(i1.transport(addr_t(0x1ff), len_t(2))); // oob ASSERT_FALSE(i1.transport(addr_t(0x200), len_t(1))); // oob ASSERT_FALSE(i1.transport(addr_t(0x800), len_t(4))); // oob } TEST_F(bus_test, multiple_targets) { bus.attach_target(t1.p_sock, 0x100, 0x1ff); bus.attach_target(t2.p_sock, 0x200, 0x7ff); // Enforce EXPECT_CALLs in sequence. testing::InSequence seq; // Check relative addressing (t1). EXPECT_CALL(t1, b_transport(gp_addr_len(addr_t(0x0), len_t(16)), _)); ASSERT_TRUE(i1.transport(addr_t(0x100), len_t(16))); EXPECT_CALL(t1, b_transport(gp_addr_len(addr_t(0x10), len_t(4)), _)); ASSERT_TRUE(i1.transport(addr_t(0x110), len_t(4))); EXPECT_CALL(t1, b_transport(gp_addr_len(addr_t(0xff), len_t(1)), _)); ASSERT_TRUE(i1.transport(addr_t(0x1ff), len_t(1))); // Check relative addressing (t2). EXPECT_CALL(t2, b_transport(gp_addr_len(addr_t(0x0), len_t(16)), _)); ASSERT_TRUE(i1.transport(addr_t(0x200), len_t(16))); EXPECT_CALL(t2, b_transport(gp_addr_len(addr_t(0x10), len_t(4)), _)); ASSERT_TRUE(i1.transport(addr_t(0x210), len_t(4))); EXPECT_CALL(t2, b_transport(gp_addr_len(addr_t(0xff), len_t(1)), _)); ASSERT_TRUE(i1.transport(addr_t(0x2ff), len_t(1))); // Overlapping t1 & t2. ASSERT_FALSE(i1.transport(addr_t(0x0), len_t(2))); // oob ASSERT_FALSE(i1.transport(addr_t(0x1ff), len_t(2))); // oob ASSERT_FALSE(i1.transport(addr_t(0x100), len_t(0x200))); // oob ASSERT_FALSE(i1.transport(addr_t(0x800), len_t(1))); // oob } TEST_F(bus_test, get_dmi_ptr) { bus.attach_target(t1.p_sock, 0x100, 0x1ff); EXPECT_CALL(t1, get_direct_mem_ptr(gp_addr_len(addr_t(0x0), len_t(16)), _)) .WillOnce([](tlm::tlm_generic_payload&, tlm::tlm_dmi& dmi) -> bool { dmi.set_start_address(0x0); dmi.set_end_address(0x1f); return true; }); auto dmi = i1.get_dmi(addr_t(0x100), len_t(16)); ASSERT_TRUE(dmi); // DMI address must also be fixed up by the bus on the bw path. ASSERT_EQ((range{0x100, 0x11f}), dmi.addr); } TEST_F(bus_test, get_dmi_ptr_larger_than_mapping) { bus.attach_target(t1.p_sock, 0x100, 0x1ff); // Returned DMI range from target exceeds bus mapping. Bus must limit returned // DMI range on the bw path. EXPECT_CALL(t1, get_direct_mem_ptr(gp_addr_len(addr_t(0x0), len_t(16)), _)) .WillOnce([](tlm::tlm_generic_payload&, tlm::tlm_dmi& dmi) -> bool { dmi.set_start_address(0x0); dmi.set_end_address(0x200); return true; }); auto dmi = i1.get_dmi(addr_t(0x100), len_t(16)); ASSERT_TRUE(dmi); // DMI address must be truncated by the bus on the bw path. ASSERT_EQ((range{0x100, 0x1ff}), dmi.addr); } TEST_F(bus_test, invalidate_dmi) { bus.attach_target(t1.p_sock, 0x400, 0x7ff); bus.attach_target(t2.p_sock, 0xa00, 0xaff); testing::InSequence seq; // Target 1. EXPECT_CALL(i1, invalidate_direct_mem_ptr(0x400, 0x410)); EXPECT_CALL(i2, invalidate_direct_mem_ptr(0x400, 0x410)); t1.inv_dmi(0x0, 0x10); EXPECT_CALL(i1, invalidate_direct_mem_ptr(0x500, 0x7ff)); EXPECT_CALL(i2, invalidate_direct_mem_ptr(0x500, 0x7ff)); t1.inv_dmi(0x100, 0x3ff); EXPECT_CALL(i1, invalidate_direct_mem_ptr(0x500, 0x7ff)); EXPECT_CALL(i2, invalidate_direct_mem_ptr(0x500, 0x7ff)); t1.inv_dmi(0x100, 0x400); EXPECT_CALL(i1, invalidate_direct_mem_ptr(0x500, 0x7ff)); EXPECT_CALL(i2, invalidate_direct_mem_ptr(0x500, 0x7ff)); t1.inv_dmi(0x100, 0x401); // Target 2. EXPECT_CALL(i1, invalidate_direct_mem_ptr(0xa00, 0xa10)); EXPECT_CALL(i2, invalidate_direct_mem_ptr(0xa00, 0xa10)); t2.inv_dmi(0x0, 0x10); EXPECT_CALL(i1, invalidate_direct_mem_ptr(0xa80, 0xaff)); EXPECT_CALL(i2, invalidate_direct_mem_ptr(0xa80, 0xaff)); t2.inv_dmi(0x80, 0xff); EXPECT_CALL(i1, invalidate_direct_mem_ptr(0xa80, 0xaff)); EXPECT_CALL(i2, invalidate_direct_mem_ptr(0xa80, 0xaff)); t2.inv_dmi(0x80, 0x100); EXPECT_CALL(i1, invalidate_direct_mem_ptr(0xa80, 0xaff)); EXPECT_CALL(i2, invalidate_direct_mem_ptr(0xa80, 0xaff)); t2.inv_dmi(0x80, 0x101); } TEST_F(bus_test, fwd_lock) { bus.attach_target(t1.p_sock, 0x400, 0x7ff); testing::InSequence seq; // Unlocked access. EXPECT_CALL( t1, b_transport(gp_addr_len_lock(addr_t(0x0), len_t(1), lock_t(false)), _)); ASSERT_TRUE(i1.transport(addr_t(0x400), len_t(1), lock_t(false))); // Locked access -> invalidates DMI pointer. EXPECT_CALL(i1, invalidate_direct_mem_ptr(0x0, -1ull)); EXPECT_CALL( t1, b_transport(gp_addr_len_lock(addr_t(0x100), len_t(1), lock_t(true)), _)); ASSERT_TRUE(i1.transport(addr_t(0x500), len_t(1), lock_t(true))); }