#ifndef SYSC_PLAYGROUND_LT_BUS #define SYSC_PLAYGROUND_LT_BUS #include "utils/range.h" #include "utils/tlm_initiator_socket_tagged.h" #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; using initiator_socket = tlm_initiator_socket_tagged; using initiator_socket_ptr = std::unique_ptr; 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. auto h = get_hierarchy_scope(); // Add new target socket to connect BUS INITIATOR. m_initiators.push_back(std::make_unique( 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. auto h = get_hierarchy_scope(); // Add new initiator socket to connect BUS TARGET. m_targets.push_back(std::make_unique( 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(); 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 m_initiators; // INITIATOR sockets to bind BUS TARGET against. 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