From b0b6a7d7f4de63fba2ba91a98cecec1bd57d03f1 Mon Sep 17 00:00:00 2001 From: Johannes Stoelp Date: Sun, 29 Oct 2023 23:05:04 +0100 Subject: test: add first lt_bus tests --- test/lt_bus.cc | 337 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 test/lt_bus.cc (limited to 'test/lt_bus.cc') diff --git a/test/lt_bus.cc b/test/lt_bus.cc new file mode 100644 index 0000000..ffbad9d --- /dev/null +++ b/test/lt_bus.cc @@ -0,0 +1,337 @@ +#include +#include + +#include + +#include +#include + +#include +#include "gmock/gmock.h" + +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; +} // namespace tag +} // namespace detail + +using addr_t = detail::phantom; +using len_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) { + tlm::tlm_generic_payload tx; + setup_tx(tx, a, l); + + 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(); + return oss.str(); +}; + +MATCHER_P(gp_addr, + /* arg0 */ arg_addr, + /* desc */ [](addr_t a) { + tlm::tlm_generic_payload tx; + tx.set_address(a.cast()); + return tx_to_str(tx); + }(arg_addr /* ign negation arg */)) { + return arg.get_address() == arg_addr.cast(); +} + +// 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(); +} + +// 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); +} -- cgit v1.2.3