diff options
-rw-r--r-- | CMakeLists.txt | 27 | ||||
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | src/models/lt_bus.h | 1 | ||||
-rw-r--r-- | src/utils/range.h | 6 | ||||
-rw-r--r-- | src/utils/types.h | 1 | ||||
-rw-r--r-- | test/lt_bus.cc | 337 |
6 files changed, 374 insertions, 3 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index cb5d232..348393e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.14) project(systemc_utils CXX) set (CMAKE_PREFIX_PATH $ENV{SYSTEMC_HOME}) @@ -13,3 +13,28 @@ foreach (X IN ITEMS event sc_export sc_export2 minimal_socket browse lt_bus) target_compile_options(${X} PRIVATE -Wall -Wextra) target_link_libraries(${X} SystemC::systemc) endforeach() + +# -- TESTING ------------------------------------------------------------------- + +# Fetch googletest. +# https://google.github.io/googletest/quickstart-cmake.html#set-up-a-project +include(FetchContent) +FetchContent_Declare( + googletest URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip +) +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +# Generate CTestTestfile.cmake. +enable_testing() +# Include gtest_discover_tests macro. +include(GoogleTest) + +foreach(T IN ITEMS lt_bus) + set(TGT test-${T}) + add_executable(${TGT} test/${T}.cc) + target_include_directories(${TGT} PRIVATE src) + target_compile_options(${TGT} PRIVATE -Wall -Wextra) + target_link_libraries(${TGT} GTest::gmock_main SystemC::systemc) + gtest_discover_tests(${TGT}) +endforeach() @@ -22,6 +22,9 @@ run: build build: INSTALL/lib/libsystemc.a BUILD/PLAYGROUND/CMakeCache.txt $(SRCS) ninja -C BUILD/PLAYGROUND $(NINJA_FLAGS) +test: build + cd BUILD/PLAYGROUND && ctest --output-on-failure + BUILD/PLAYGROUND/CMakeCache.txt: CMakeLists.txt cmake -B BUILD/PLAYGROUND $@ -S . \ -G Ninja \ @@ -50,7 +53,7 @@ systemc: # -- UTIL ---------------------------------------------------------------------- fmt: - clang-format -i $(shell find src -type f) + clang-format -i $(shell find src test -type f) # -- CLEAN --------------------------------------------------------------------- diff --git a/src/models/lt_bus.h b/src/models/lt_bus.h index c533c50..a8391f8 100644 --- a/src/models/lt_bus.h +++ b/src/models/lt_bus.h @@ -47,6 +47,7 @@ class lt_bus : public sc_core::sc_module { void attach_target(tlm::tlm_base_target_socket_b<>& target, u64 start, u64 end) { + assert(start <= end); const range addr{start, end}; // Check if new range overlaps with any registered memory map range. diff --git a/src/utils/range.h b/src/utils/range.h index ce25298..e4ef12f 100644 --- a/src/utils/range.h +++ b/src/utils/range.h @@ -7,7 +7,7 @@ struct range { constexpr explicit range(u64 start, u64 end) : start{start}, end{end} { - assert(start < end); + assert(start <= end); } constexpr bool overlaps(range rhs) const { @@ -18,6 +18,10 @@ struct range { return start <= rhs.start && rhs.end <= end; } + constexpr bool operator==(range rhs) const { + return start == rhs.start && end == rhs.end; + } + u64 start; u64 end; }; diff --git a/src/utils/types.h b/src/utils/types.h index a13b8ef..4f824f6 100644 --- a/src/utils/types.h +++ b/src/utils/types.h @@ -3,6 +3,7 @@ #include <cstdint> +using u8 = std::uint8_t; using u32 = std::uint32_t; using u64 = std::uint64_t; 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 <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <models/lt_bus.h> + +#include <tlm_utils/simple_initiator_socket.h> +#include <tlm_utils/simple_target_socket.h> + +#include <sstream> +#include "gmock/gmock.h" + +using testing::_; + +// -- STRONG TYPES ------------------------------------------------------------- + +namespace detail { +template <typename Tag, typename T> +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<detail::tag::addr, sc_dt::uint64>; +using len_t = detail::phantom<detail::tag::len, u32>; + +// -- 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<target> 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<initiator> 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<target> t1{"target1"}; + testing::StrictMock<target> 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); +} |