#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>
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;
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 ----------------------------------------------------------
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, 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.
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();
if (const bus_lock* ext = tx.get_extension<bus_lock>()) {
oss << ",tx.is_lock=" << ext->is_lock;
}
return oss.str();
};
// 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();
}
// 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
// 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);
}
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)));
}