From 291364e1b408d29236034854b7ed30f080e5b6c9 Mon Sep 17 00:00:00 2001 From: Johannes Stoelp Date: Wed, 1 Nov 2023 18:41:44 +0100 Subject: lt_bus: add initial support for global bus locking --- src/models/lt_bus.h | 101 ++++++++++++++++++++++++++++-- test/CMakeLists.txt | 44 ++++++++++--- test/lt_bus.cc | 66 +++++++++++++++----- test/lt_bus_locked.cc | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 349 insertions(+), 30 deletions(-) create mode 100644 test/lt_bus_locked.cc diff --git a/src/models/lt_bus.h b/src/models/lt_bus.h index cf09d99..86b181e 100644 --- a/src/models/lt_bus.h +++ b/src/models/lt_bus.h @@ -7,10 +7,42 @@ #include "utils/tlm_target_socket_tagged.h" #include "utils/types.h" +#include + #include #include #include +// TLM bus lock extension. +// +// This extension is used to implemented the bus locking scheme. +// The protocol is as follows. Once an initiator sends a locked transaction, +// the bus will be locked by that initiator after all currently pending +// transactions are finished. The bus is locked until the locking initiator +// sends an unlocked transaction. +struct [[nodiscard]] bus_lock : tlm::tlm_extension { + bool is_lock{false}; + + constexpr explicit bus_lock() = default; + constexpr explicit bus_lock(const bus_lock&) = default; + + virtual tlm_extension_base* clone() const override { + return new bus_lock(*this); + } + + virtual void copy_from(const tlm_extension_base& ext) override { + if (&ext == this) { + // Copy from self, nop. + return; + } + + assert(typeid(this) == typeid(ext)); + const bus_lock& other = static_cast(ext); + + is_lock = other.is_lock; + } +}; + class lt_bus : public sc_core::sc_module { using target_socket = tlm_target_socket_tagged; using target_socket_ptr = std::unique_ptr; @@ -116,14 +148,60 @@ class lt_bus : public sc_core::sc_module { // -- TLM_FW_TRANSPORT_IF ---------------------------------------------------- - void b_transport(usize, tlm::tlm_generic_payload& tx, sc_core::sc_time& t) { + void b_transport(usize idx, + tlm::tlm_generic_payload& tx, + sc_core::sc_time& t) { if (const auto r = decode(tx)) { - const auto tx_start = tx.get_address(); - assert(r.start <= tx_start); + const auto do_tx = [&]() { + const auto tx_start = tx.get_address(); + assert(r.start <= tx_start); - tx.set_address(tx_start - r.start); - (*r.sock)->b_transport(tx, t); - tx.set_address(tx_start); + tx.set_address(tx_start - r.start); + (*r.sock)->b_transport(tx, t); + tx.set_address(tx_start); + }; + + while (m_bl.is_locked && (m_bl.idx != idx)) { + wait(m_bl.ev_no_pending_tx); + } + + const auto* ext = tx.get_extension(); + if (ext && ext->is_lock) { + assert(!m_bl.is_locked); + + m_bl.is_locked = true; + m_bl.idx = idx; + + if (m_bl.pending_tx) { + wait(m_bl.ev_no_pending_tx); + } + assert(m_bl.pending_tx == 0); + + // Invalidate DMI pointers for the whole address space. This forces all + // initiators on the slow path and through the bus, which can than + // enforce the exclusive access. + for (auto& sock : m_initiators) { + (*sock)->invalidate_direct_mem_ptr(0, -1ull); + } + + do_tx(); + assert(m_bl.pending_tx == 0); + } else { + assert(!m_bl.is_locked || (m_bl.is_locked && m_bl.idx == idx)); + + m_bl.pending_tx++; + do_tx(); + m_bl.pending_tx--; + + if (m_bl.is_locked && m_bl.idx == idx) { + assert(m_bl.pending_tx == 0); + m_bl.is_locked = false; + } + + if (m_bl.pending_tx == 0) { + m_bl.ev_no_pending_tx.notify(sc_core::SC_ZERO_TIME); + } + } } else { tx.set_response_status(tlm::TLM_ADDRESS_ERROR_RESPONSE); } @@ -132,6 +210,10 @@ class lt_bus : public sc_core::sc_module { bool get_direct_mem_ptr(usize, tlm::tlm_generic_payload& tx, tlm::tlm_dmi& dmi) { + if (m_bl.is_locked) { + return false; + } + if (const auto r = decode(tx)) { const auto tx_start = tx.get_address(); assert(r.start <= tx_start); @@ -245,6 +327,13 @@ class lt_bus : public sc_core::sc_module { std::vector m_targets; // Address range mappings to BUS TARGETs (m_tragets). std::vector m_mappings; + + struct bus_lock_state { + bool is_locked{false}; + usize idx{0}; + usize pending_tx{0}; + sc_core::sc_event ev_no_pending_tx; + } m_bl; }; #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b67735a..714e18a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,8 +1,10 @@ -# Fetch googletest. +# Fetch googletest (latest greatest, okay in this repo). # 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 + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG main ) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) @@ -12,11 +14,33 @@ enable_testing() # Include gtest_discover_tests macro. include(GoogleTest) -foreach(T IN ITEMS lt_bus) - set(TGT test-${T}) - add_executable(${TGT} ${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() +macro(test_int name kind) + set(target test-${name}) + add_executable(${target} ${name}.cc) + target_include_directories(${target} PRIVATE ${SRC}) + target_compile_options(${target} PRIVATE -Wall -Wextra) + if(${kind} STREQUAL "gtest") + target_link_libraries(${target} GTest::gmock_main) + target_link_libraries(${target} SystemC::systemc) + gtest_discover_tests(${target}) + elseif(${kind} STREQUAL "sim") + target_link_libraries(${target} SystemC::systemc) + add_test(NAME ${target} COMMAND ${target}) + else() + message(FATAL_ERROR "unknown test kind '${kind}'") + endif() +endmacro() + +macro(sim_test name) + test_int(${name} "sim") +endmacro() + +macro(gtest_test name) + test_int(${name} "gtest") +endmacro() + +# Google tests. +gtest_test("lt_bus") + +# Simulation tests. +sim_test("lt_bus_locked") diff --git a/test/lt_bus.cc b/test/lt_bus.cc index ffbad9d..4f7fcf7 100644 --- a/test/lt_bus.cc +++ b/test/lt_bus.cc @@ -7,7 +7,6 @@ #include #include -#include "gmock/gmock.h" using testing::_; @@ -28,11 +27,13 @@ struct [[nodiscard]] phantom { 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 ---------------------------------------------------------- @@ -75,10 +76,15 @@ struct initiator : public sc_core::sc_module { MOCK_METHOD(void, invalidate_direct_mem_ptr, (sc_dt::uint64, sc_dt::uint64)); - bool transport(addr_t a, len_t l) { + 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. @@ -130,21 +136,14 @@ struct initiator : public sc_core::sc_module { 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(); + 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(); }; -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, @@ -159,6 +158,26 @@ MATCHER_P2(gp_addr_len, 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 @@ -335,3 +354,22 @@ TEST_F(bus_test, invalidate_dmi) { 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))); +} diff --git a/test/lt_bus_locked.cc b/test/lt_bus_locked.cc new file mode 100644 index 0000000..55357a8 --- /dev/null +++ b/test/lt_bus_locked.cc @@ -0,0 +1,168 @@ +#include "models/lt_bus.h" +#include "utils/log.h" + +#include +#include + +using sc_core::SC_NS; +using sc_core::sc_time; +using sc_core::sc_time_stamp; +using sc_core::SC_ZERO_TIME; + +// -- TARGET ------------------------------------------------------------------- + +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); + p_sock.register_get_direct_mem_ptr(this, &target::get_direct_mem_ptr); + } + + tlm_utils::simple_target_socket p_sock{"sock"}; + + private: + void b_transport(tlm::tlm_generic_payload& tx, sc_core::sc_time&) { + CLOGM(YELLOW, "transport 0x%llx w: %d r: %d", tx.get_address(), + tx.is_write(), tx.is_read()); + + // Miss-use byte enable len for sleep duration. + if (u32 len = tx.get_byte_enable_length()) { + CLOGM(YELLOW, "wait(%u)", len); + wait(sc_time(len, SC_NS)); + } + + tx.set_response_status(tlm::TLM_OK_RESPONSE); + } + + bool get_direct_mem_ptr(tlm::tlm_generic_payload&, tlm::tlm_dmi&) { + return true; + } +}; + +// -- INITIATOR ---------------------------------------------------------------- + +struct initiator : public sc_core::sc_module { + SC_HAS_PROCESS(initiator); + + explicit initiator(sc_core::sc_module_name nm) : sc_module(std::move(nm)) { + SC_THREAD(run0); + SC_THREAD(run1); + SC_THREAD(run2); + } + + tlm_utils::simple_initiator_socket p_sock0{"sock0"}; + tlm_utils::simple_initiator_socket p_sock1{"sock1"}; + tlm_utils::simple_initiator_socket p_sock2{"sock2"}; + + private: + void setup_tx(tlm::tlm_generic_payload& tx, u64 addr, u32 len) const { + tx.set_command(tlm::TLM_WRITE_COMMAND); + tx.set_address(addr); + tx.set_data_ptr(nullptr); + tx.set_data_length(4); + tx.set_byte_enable_ptr(nullptr); + tx.set_byte_enable_length(len); + tx.set_response_status(tlm::TLM_INCOMPLETE_RESPONSE); + tx.set_dmi_allowed(false); + } + + void send_tx(tlm_utils::simple_initiator_socket& sock, + u64 addr, + u32 len, + bool lock = false) { + tlm::tlm_generic_payload tx; + setup_tx(tx, addr, len); + + auto* bl = new bus_lock; + bl->is_lock = lock; + tx.set_extension(bl); + + CLOGM(CYAN, "[%s] ACCESS @0x%lx lock=%d", sock.name(), addr, lock); + + sc_core::sc_time t; + sock->b_transport(tx, t); + + CLOGM(CYAN, "[%s] ACCESS ok: %d (%s)", sock.name(), tx.is_response_ok(), + tx.get_response_string().c_str()); + } + + bool get_dmi(tlm_utils::simple_initiator_socket& sock, u64 addr) { + tlm::tlm_generic_payload tx; + setup_tx(tx, addr, 1 /* len */); + + tlm::tlm_dmi dmi; + return sock->get_direct_mem_ptr(tx, dmi); + } + + void run0() { + // Lock the bus. + send_tx(p_sock0, 0x1000, 0, true); // @0ns + // Spend some time, keep the bus locked. + wait(500, SC_NS); + // Unlock the bus. + send_tx(p_sock0, 0x1000, 0, false); // @500ns + assert(sc_time_stamp() == sc_time(500, SC_NS)); + + // -- 500ns -- + + wait(10, SC_NS); + LOGM("------------------------------------------------------------"); + wait(90, SC_NS); + + // Waiting for bus lock, as run1 & run2 have currently an outstanding tx. + assert(sc_time_stamp() == sc_time(600, SC_NS)); + send_tx(p_sock0, 0x1000, 0, true); // @600ns + assert(sc_time_stamp() == sc_time(750, SC_NS)); + } + + void run1() { + wait(10, SC_NS); + // No dmi while bus is locked. + assert(get_dmi(p_sock1, 0x2000) == false); + + send_tx(p_sock1, 0x2000, 0); // @10ns + assert(sc_time_stamp() == sc_time(500, SC_NS)); + + // -- 500ns -- + + wait(50, SC_NS); + send_tx(p_sock1, 0x2000, 100); // @550ns + assert(sc_time_stamp() == sc_time(650, SC_NS)); + } + + void run2() { + wait(20, SC_NS); + // No dmi while bus is locked. + assert(get_dmi(p_sock2, 0x3000) == false); + + send_tx(p_sock2, 0x3000, 0); + assert(sc_time_stamp() == sc_time(500, SC_NS)); + + // -- 500ns -- + + wait(50, SC_NS); + send_tx(p_sock1, 0x3000, 200); // @550ns + assert(sc_time_stamp() == sc_time(750, SC_NS)); + } +}; + +// -- SC_MAIN ------------------------------------------------------------------ + +extern "C" int sc_main(int, char*[]) { + lt_bus bus{"bus"}; + + target target1{"target1"}; + + initiator init1{"init1"}; + + bus.attach_target(target1.p_sock, 0x0000, 0xffff); + + bus.attach_initiator(init1.p_sock0); + bus.attach_initiator(init1.p_sock1); + bus.attach_initiator(init1.p_sock2); + + bus.show_mmap(); + + sc_core::sc_start(); + + return 0; +} -- cgit v1.2.3