aboutsummaryrefslogblamecommitdiff
path: root/test/lt_bus.cc
blob: ffbad9d9ed1b4580661d9736ac17563b907a800f (plain) (tree)
















































































































































































































































































































































                                                                                
#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);
}