File mem.h
File List > include > simtix > mem.h
Go to the documentation of this file
#pragma once
#include <simtix/clocked.h>
#include <simtix/statistics.h>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "fmt/core.h"
namespace simtix {
namespace mem {
class MemoryInterface : public sim::Clocked {
public:
struct Payload {
union {
uint32_t wid;
} id;
uint64_t addr;
uint8_t *data;
uint8_t *strb;
size_t size;
};
enum class RespStatus : uint8_t {
kOkay, // Target is able to execute the command successfully.
kAddrError, // Target is unable to act upon the address attribute, or
// address out-of-range.
kCmdError, // Target is unable to execute the command (read/write).
kGenericError // Any other error.
};
using OnResp = std::function<bool(RespStatus)>;
explicit MemoryInterface(const std::string &name,
sim::TickPri pri = sim::kMemTickPri)
: sim::Clocked(name, pri) {}
virtual ~MemoryInterface() = default;
virtual uint8_t &operator[](const uint64_t addr) = 0;
virtual bool Read(Payload payload, OnResp on_resp) { return false; }
virtual bool Write(Payload payload, OnResp on_resp) { return false; }
};
class SimpleMemory : public MemoryInterface {
public:
struct Param {
size_t kSizeBytes; // Memory size in bytes.
uint32_t kLatencyCycles; // Latency in cycles.
size_t kOutputFifoDepth; // Depth of output FIFO buffer
};
// 1 MB, no latency
inline static const Param kDefaultParam = {
.kSizeBytes = 1024 * 1024, .kLatencyCycles = 0, .kOutputFifoDepth = 2};
explicit SimpleMemory(const std::string &name,
const Param ¶m = kDefaultParam);
~SimpleMemory();
SimpleMemory(const SimpleMemory &s) = delete;
SimpleMemory operator=(const SimpleMemory &s) = delete;
bool Read(Payload payload, OnResp on_resp) override;
bool Write(Payload payload, OnResp on_resp) override;
uint8_t &operator[](const uint64_t addr) override;
protected:
void Tick() override;
bool HasPendingTasks() override;
class Impl;
std::unique_ptr<Impl> impl_;
};
class BankedMemory : public MemoryInterface {
public:
struct Param {
size_t kBanks; // Number of banks.
size_t kSizeBytes; // Total memory size across banks in bytes.
uint32_t kInterleaveGranularity; // Interleave granularity in bytes.
uint32_t kLatencyCycles; // Latency in cycles.
size_t kOutputFifoDepth; // Output FIFO depth.
};
// 1 MB, no latency
inline static const Param kDefaultParam = {.kBanks = 4,
.kSizeBytes = 1024 * 1024,
.kInterleaveGranularity = 64,
.kLatencyCycles = 0,
.kOutputFifoDepth = 2};
explicit BankedMemory(const std::string &name,
const Param ¶m = kDefaultParam);
~BankedMemory();
BankedMemory(const BankedMemory &s) = delete;
BankedMemory operator=(const BankedMemory &s) = delete;
bool Read(Payload payload, OnResp on_resp) override;
bool Write(Payload payload, OnResp on_resp) override;
uint8_t &operator[](const uint64_t addr) override;
const stat::Group *stat() const;
protected:
void Tick() override;
bool HasPendingTasks() override;
class Impl;
std::unique_ptr<Impl> impl_;
};
class CacheInterface : public MemoryInterface {
public:
explicit CacheInterface(const std::string &name,
sim::TickPri pri = sim::kMemTickPri)
: MemoryInterface(name, pri) {}
virtual ~CacheInterface() = default;
virtual bool Flush(OnResp on_resp) = 0;
virtual void AttachNextLevel(MemoryInterface *next_level) = 0;
virtual const std::shared_ptr<stat::Group> stat() const = 0;
virtual void ResetStat() = 0;
};
class Cache : public CacheInterface {
public:
// Cache parameters
struct Param {
// Replacement policies
enum class ReplacementPolicies : uint8_t { kRpRandom, kRpFIFO, kRpLRU };
// Write hit policies
enum class WriteHitPolicies : uint8_t { kWriteBack = 0, kWriteThrough };
// Write hit policies
enum class WriteMissPolicies : uint8_t { kWriteAllocate = 0, kWriteAround };
// Entry for non-cacheable regions
struct NonCacheableEntry {
uint64_t addr;
size_t size;
};
// Banking configuration
uint32_t kBanks; // number of banks
size_t kSizeBytes; // memory size in bytes
uint32_t kWays; // number of ways
size_t kBlockSizeBytes; // block size in bytes
ReplacementPolicies kReplacementPolicy;
WriteHitPolicies kWriteHitPolicy;
WriteMissPolicies kWriteMissPolicy;
// MSHR
uint32_t kMSHRs; // number of MSHRs
uint32_t kMshrCoreReqQueueDepth;
// Queues
uint32_t kCoreReqQueueDepth;
uint32_t kCoreRespQueueDepth;
uint32_t kDataArrayReqQueueDepth;
uint32_t kMemRespQueueDepth;
// Write buffer
uint32_t kWriteBuffers;
// Non-cacheable related queue depth
uint32_t kNonCacheableQueueDepth;
// Non-cacheable regions
std::vector<NonCacheableEntry> kNonCacheableRegions;
};
inline static const Param kDefaultParam = {
.kBanks = 1,
.kSizeBytes = 4096,
.kWays = 4,
.kBlockSizeBytes = 64,
.kReplacementPolicy = Param::ReplacementPolicies::kRpRandom,
.kWriteHitPolicy = Param::WriteHitPolicies::kWriteBack,
.kWriteMissPolicy = Param::WriteMissPolicies::kWriteAllocate,
.kMSHRs = 4,
.kMshrCoreReqQueueDepth = 16,
.kCoreReqQueueDepth = 2,
.kCoreRespQueueDepth = 2,
.kDataArrayReqQueueDepth = 2,
.kMemRespQueueDepth = 2,
.kWriteBuffers = 4,
.kNonCacheableQueueDepth = 2,
.kNonCacheableRegions = {},
};
explicit Cache(const std::string &name, MemoryInterface *next_level = nullptr,
const Param ¶m = kDefaultParam);
~Cache();
uint8_t &operator[](const uint64_t addr) override;
bool Read(Payload payload, OnResp on_resp) override;
bool Write(Payload payload, OnResp on_resp) override;
bool Flush(OnResp on_resp) override;
void AttachNextLevel(MemoryInterface *next_level) override;
void ResetStat() override;
const std::shared_ptr<stat::Group> stat() const override;
protected:
void Tick() override;
bool HasPendingTasks() override;
class Impl;
std::unique_ptr<Impl> impl_;
};
class NBHBCache : public CacheInterface {
public:
using Param = Cache::Param;
inline static const Param kDefaultParam = {
.kBanks = 4,
.kSizeBytes = 4096,
.kWays = 4,
.kBlockSizeBytes = 64,
.kReplacementPolicy = Param::ReplacementPolicies::kRpRandom,
.kWriteHitPolicy = Param::WriteHitPolicies::kWriteBack,
.kWriteMissPolicy = Param::WriteMissPolicies::kWriteAllocate,
.kMSHRs = 4,
.kMshrCoreReqQueueDepth = 16,
.kCoreReqQueueDepth = 2,
.kCoreRespQueueDepth = 2,
.kDataArrayReqQueueDepth = 2,
.kMemRespQueueDepth = 2,
.kWriteBuffers = 4,
.kNonCacheableQueueDepth = 2,
.kNonCacheableRegions = {},
};
explicit NBHBCache(const std::string &name, MemoryInterface *next_level,
const Param ¶m);
~NBHBCache();
uint8_t &operator[](const uint64_t addr) override;
bool Read(Payload payload, OnResp on_resp) override;
bool Write(Payload payload, OnResp on_resp) override;
bool Flush(OnResp on_resp) override;
void AttachNextLevel(MemoryInterface *next_level) override;
void ResetStat() override;
const std::shared_ptr<stat::Group> stat() const override;
protected:
void Tick() override;
bool HasPendingTasks() override;
class Impl;
std::unique_ptr<Impl> impl_;
};
class XBar : public MemoryInterface {
public:
explicit XBar(const std::string &name, uint32_t bandwidth);
~XBar();
bool AddSlave(MemoryInterface *, uint64_t addr, size_t size);
bool Read(Payload payload, OnResp on_resp) override;
bool Write(Payload payload, OnResp on_resp) override;
uint8_t &operator[](const uint64_t addr) override;
std::optional<MemoryInterface *> Route(uint64_t addr, size_t size);
bool Valid(uint64_t addr, size_t size);
private:
void Tick() override;
bool HasPendingTasks() override;
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace mem
} // namespace simtix
namespace fmt {
// fmt support for replacement policies
template <>
struct formatter<simtix::mem::Cache::Param::ReplacementPolicies>
: formatter<string_view> {
template <typename FormatContext>
auto format(simtix::mem::Cache::Param::ReplacementPolicies rp,
FormatContext &ctx) const { // NOLINT
string_view name = "Unknown";
switch (rp) {
case simtix::mem::Cache::Param::ReplacementPolicies::kRpLRU:
name = "LRU";
break;
case simtix::mem::Cache::Param::ReplacementPolicies::kRpFIFO:
name = "FIFO";
break;
case simtix::mem::Cache::Param::ReplacementPolicies::kRpRandom:
name = "Random";
break;
}
return formatter<string_view>::format(name, ctx);
}
};
// fmt support for write hit policies
template <>
struct formatter<simtix::mem::Cache::Param::WriteHitPolicies>
: formatter<string_view> {
template <typename FormatContext>
auto format(simtix::mem::Cache::Param::WriteHitPolicies wp,
FormatContext &ctx) const { // NOLINT
string_view name = "Unknown";
switch (wp) {
case simtix::mem::Cache::Param::WriteHitPolicies::kWriteBack:
name = "WriteBack";
break;
case simtix::mem::Cache::Param::WriteHitPolicies::kWriteThrough:
name = "WriteThrough";
break;
}
return formatter<string_view>::format(name, ctx);
}
};
// fmt support for write miss policies
template <>
struct formatter<simtix::mem::Cache::Param::WriteMissPolicies>
: formatter<string_view> {
template <typename FormatContext>
auto format(simtix::mem::Cache::Param::WriteMissPolicies wp,
FormatContext &ctx) const { // NOLINT
string_view name = "Unknown";
switch (wp) {
case simtix::mem::Cache::Param::WriteMissPolicies::kWriteAround:
name = "WriteAround";
break;
case simtix::mem::Cache::Param::WriteMissPolicies::kWriteAllocate:
name = "WriteAllocate";
break;
}
return formatter<string_view>::format(name, ctx);
}
};
} // namespace fmt