Skip to content

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 &param = 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 &param = 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 &param = 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 &param);

  ~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