#ifndef SYSC_PLAYGROUND_LT_BUS
#define SYSC_PLAYGROUND_LT_BUS
#include "utils/range.h"
#include "utils/sysc.h"
#include "utils/tlm_initiator_socket_tagged.h"
#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>;
using initiator_socket = tlm_initiator_socket_tagged<lt_bus>;
using initiator_socket_ptr = std::unique_ptr<initiator_socket>;
public:
explicit lt_bus(sc_core::sc_module_name nm)
: sc_core::sc_module(std::move(nm)) {}
// -- ATTACH BUS INITIATOR ---------------------------------------------------
void attach_initiator(tlm::tlm_base_initiator_socket_b<>& init) {
const usize id = m_initiators.size();
const std::string name = "init" + std::to_string(id);
{
// Add current module on top of module stack for tlm sockets.
scoped_push_hierarchy h(*this);
// Add new target socket to connect BUS INITIATOR.
m_initiators.push_back(std::make_unique<target_socket>(
name.c_str(), id, this, <_bus::b_transport,
<_bus::get_direct_mem_ptr, <_bus::transport_dbg));
}
// Bind sockets.
auto& target = m_initiators.back();
target->bind(init);
}
// -- ATTACH BUS TARGET ------------------------------------------------------
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.
for (const auto& mmap : m_mappings) {
if (mmap.addr.overlaps(addr)) {
std::fprintf(stderr,
"lt_bus: memory map conflict detected\n"
"old: %08lx - %08lx\n"
"new: %08lx - %08lx\n",
mmap.addr.start, mmap.addr.end, start, end);
std::abort();
}
}
const usize id = m_targets.size();
const std::string name = "target" + std::to_string(id);
{
// Add current module on top of module stack for tlm sockets.
scoped_push_hierarchy h(*this);
// Add new initiator socket to connect BUS TARGET.
m_targets.push_back(std::make_unique<initiator_socket>(
name.c_str(), id, this, <_bus::invalidate_direct_mem_ptr));
}
// Bind sockets.
auto& init = m_targets.back();
init->bind(target);
// Insert new mapping, id is equal to idx into socket vector.
m_mappings.push_back({addr, id});
}
// -- SHOW_MMAP --------------------------------------------------------------
void show_mmap() const {
// Dump memory map.
for (const auto& mmap : m_mappings) {
std::printf("%08lx - %08lx :[%2ld] %s\n", mmap.addr.start, mmap.addr.end,
mmap.idx, m_targets[mmap.idx].get()->name());
}
}
private:
// -- TLM_BW_TRANSPORT_IF ----------------------------------------------------
void invalidate_direct_mem_ptr(usize id,
sc_dt::uint64 start,
sc_dt::uint64 end) {
assert(start <= end);
for (const auto& mmap : m_mappings) {
if (mmap.idx != id) {
continue;
}
// Compute absolute start/end address based on memory map.
const range abs_addr =
compute_abs_mmap_address(range{start, end}, mmap.addr);
for (auto& sock : m_initiators) {
(*sock)->invalidate_direct_mem_ptr(abs_addr.start, abs_addr.end);
}
}
}
// -- TLM_FW_TRANSPORT_IF ----------------------------------------------------
void b_transport(usize idx,
tlm::tlm_generic_payload& tx,
sc_core::sc_time& t) {
if (const auto r = decode(tx)) {
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);
};
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);
}
}
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);
tx.set_address(tx_start - r.start);
const bool ok = (*r.sock)->get_direct_mem_ptr(tx, dmi);
tx.set_address(tx_start);
// Early return, dmi request failed, no need to fixup dmi addresses.
if (!ok) {
return false;
}
// Compute absolute start/end address based on memory map.
const range abs_addr = compute_abs_mmap_address(
range{dmi.get_start_address(), dmi.get_end_address()},
range{r.start, r.end});
// Update dmi payload with absolute addresses.
dmi.set_start_address(abs_addr.start);
dmi.set_end_address(abs_addr.end);
return true;
}
return false;
}
unsigned int transport_dbg(usize, tlm::tlm_generic_payload& tx) {
unsigned int ret = 0;
if (const auto r = decode(tx)) {
const auto tx_start = tx.get_address();
assert(r.start <= tx_start);
tx.set_address(tx_start - r.start);
ret = (*r.sock)->transport_dbg(tx);
tx.set_address(tx_start);
}
return ret;
}
// -- DECODE BUS TARGET ------------------------------------------------------
struct decode_result {
initiator_socket* sock{nullptr};
u64 start{0ull};
u64 end{0ull};
constexpr explicit operator bool() const {
return sock != nullptr;
}
};
decode_result decode(range addr) const {
for (const auto& mmap : m_mappings) {
if (mmap.addr.contains(addr)) {
return {m_targets[mmap.idx].get(), mmap.addr.start, mmap.addr.end};
}
}
return {};
}
decode_result decode(const tlm::tlm_generic_payload& tx) const {
const u64 start = tx.get_address();
const u64 end = start + tx.get_data_length() - 1;
return decode(range{start, end});
}
// -- COMPUTE ABSOLUTE MEMORY MAP ADDRESS ------------------------------------
static range compute_abs_mmap_address(range rel, range mmap) {
// Compute absolute start address.
const sc_dt::uint64 abs_start = mmap.start + rel.start;
// Start address must be in bounds of the memory map mapping.
assert(abs_start <= mmap.end);
// Compute absolute end address. Limit by memory map if range exceeded.
const auto comp_abs_end = [&mmap](sc_dt::uint64 abs_end) -> sc_dt::uint64 {
if (abs_end > mmap.end /* exceeds mapping */ ||
abs_end < mmap.start /* wraps around */) {
return mmap.end;
}
return abs_end;
};
const sc_dt::uint64 abs_end = comp_abs_end(mmap.start + rel.end);
return range{abs_start, abs_end};
}
// -- SC_MODULE CALLBACKS ----------------------------------------------------
virtual void start_of_simulation() override {
// Sort memory mappings by start address.
std::sort(m_mappings.begin(), m_mappings.end(),
[](const mapping& lhs, const mapping& rhs) {
return lhs.addr.start < rhs.addr.start;
});
}
// -- LOCAL CLASSES ----------------------------------------------------------
struct mapping {
range addr;
usize idx;
};
// -- MEMBER -----------------------------------------------------------------
// TARGET sockets to bind BUS INITIATORS against.
std::vector<target_socket_ptr> m_initiators;
// INITIATOR sockets to bind BUS TARGET against.
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