aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Stoelp <johannes.stoelp@gmail.com>2023-11-01 18:41:44 +0100
committerJohannes Stoelp <johannes.stoelp@gmail.com>2023-11-01 18:41:44 +0100
commit291364e1b408d29236034854b7ed30f080e5b6c9 (patch)
treef55efb5954ea6374295f2e04d98041b766992660
parentccaae5eb310ae8aabd77f8fe53f181f4afe0365b (diff)
downloadsysc-playground-291364e1b408d29236034854b7ed30f080e5b6c9.tar.gz
sysc-playground-291364e1b408d29236034854b7ed30f080e5b6c9.zip
lt_bus: add initial support for global bus locking
-rw-r--r--src/models/lt_bus.h101
-rw-r--r--test/CMakeLists.txt44
-rw-r--r--test/lt_bus.cc66
-rw-r--r--test/lt_bus_locked.cc168
4 files changed, 349 insertions, 30 deletions
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 <sysc/kernel/sc_event.h>
+
#include <algorithm>
#include <memory>
#include <vector>
+// 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<bus_lock> {
+ 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<const bus_lock&>(ext);
+
+ is_lock = other.is_lock;
+ }
+};
+
class lt_bus : public sc_core::sc_module {
using target_socket = tlm_target_socket_tagged<lt_bus>;
using target_socket_ptr = std::unique_ptr<target_socket>;
@@ -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<bus_lock>();
+ 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<initiator_socket_ptr> m_targets;
// Address range mappings to BUS TARGETs (m_tragets).
std::vector<mapping> 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 <tlm_utils/simple_target_socket.h>
#include <sstream>
-#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<detail::tag::addr, sc_dt::uint64>;
using len_t = detail::phantom<detail::tag::len, u32>;
+using lock_t = detail::phantom<detail::tag::len, bool>;
// -- 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<bus_lock>()) {
+ 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<bus_lock>();
+ 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 <tlm_utils/simple_initiator_socket.h>
+#include <tlm_utils/simple_target_socket.h>
+
+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<target> 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<initiator> p_sock0{"sock0"};
+ tlm_utils::simple_initiator_socket<initiator> p_sock1{"sock1"};
+ tlm_utils::simple_initiator_socket<initiator> 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<initiator>& 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<initiator>& 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;
+}