aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt27
-rw-r--r--Makefile5
-rw-r--r--src/models/lt_bus.h1
-rw-r--r--src/utils/range.h6
-rw-r--r--src/utils/types.h1
-rw-r--r--test/lt_bus.cc337
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()
diff --git a/Makefile b/Makefile
index ec155df..ea6e11f 100644
--- a/Makefile
+++ b/Makefile
@@ -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);
+}