// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#ifndef OPENTITAN_HW_DV_VERILATOR_CPP_MEM_AREA_H_
#define OPENTITAN_HW_DV_VERILATOR_CPP_MEM_AREA_H_

#include <cstdint>
#include <string>
#include <vector>

// This is the maximum width of a memory that's supported by the code in
// prim_util_memload.svh
#define SV_MEM_WIDTH_BITS 312

// This is the number of bytes to reserve for buffers used to pass data between
// C++ and SystemVerilog using prim_util_memload.svh. Since this goes over DPI
// using the svBitVecVal type, we have to round up to the next 32-bit word.
#define SV_MEM_WIDTH_BYTES (4 * ((SV_MEM_WIDTH_BITS + 31) / 32))

/**
 * A "memory area", representing a memory in the simulated design.
 */
class MemArea {
 public:
  /** Constructor
   *
   * @param scope  The SystemVerilog scope where the instantiated memory can be
   *               found. This needs to support the DPI-C interfaces \c
   *               simutil_memload and \c simutil_set_mem (used for vmem and
   *               ELF files, respectively).
   *
   * @param size   The size of the memory in bytes (must be positive and a
   *               multiple of \p width_byte)
   *
   * @param size   The width of each entry in bytes (must be positive)
   */
  MemArea(const std::string &scope, uint32_t size, uint32_t width_byte);

  virtual ~MemArea() {}

  /** Write data to this memory area at the given word offset
   *
   * This assumes that the result will fit in the memory. If the scope cannot
   * be set, this throws an SVScoped::Error. If a call to \c simutil_set_mem
   * fails, this throws a \c std::runtime_error.
   *
   * @param word_offset The offset, in words, of the first word that should be
   *                    written.
   *
   * @param data        The data that should be written. If the length is not a
   *                    multiple of \p width_byte, the last word will be
   *                    zero-extended.
   */
  virtual void Write(uint32_t word_offset,
                     const std::vector<uint8_t> &data) const;

  /** Read data from this memory area, starting at the given offset.
   *
   * This assumes that there are <tt>word_offset + num_words</tt> words in the
   * memory. Returns a vector with <tt>num_words * width_byte_</tt> elements.
   *
   * If the scope cannot be set, this throws an SVScoped::Error. If a call to
   * simutil_get_mem fails, this throws a std::runtime_error.
   *
   * @param word_offset The offset, in words, of the first word that should be
   *                    written.
   *
   * @param num_words   The number of words to read.
   */
  virtual std::vector<uint8_t> Read(uint32_t word_offset,
                                    uint32_t num_words) const;

  /** Use \c simutil_memload to load a vmem file into the memory */
  virtual void LoadVmem(const std::string &path) const;

  const std::string &GetScope() const { return scope_; }
  uint32_t GetSizeWords() const { return num_words_; }
  uint32_t GetSizeBytes() const { return num_words_ * width_byte_; }
  uint32_t GetWidthByte() const { return width_byte_; }
  uint32_t GetWidth() const { return 8 * width_byte_; }

 protected:
  std::string scope_;    ///< Design scope (used for accesses over DPI)
  uint32_t num_words_;   ///< Size of the memory area in words
  uint32_t width_byte_;  ///< Size of each word in bytes

  /** Write to buf with the data that should be copied to the physical memory
   * for a single memory word.
   *
   * The default implementation just uses memcpy to copy the data across. Other
   * implementations might add scrambling, ECC bits or similar. This must write
   * every bit of buf that will be used by the memory, but needn't clear bits
   * further up (this is done outside of the loop).
   *
   * @param buf       Destination buffer
   * @param data      A large buffer that contains the data to be written
   * @param start_idx An offset into \p data for the start of the memory word
   * @param dst_word  Logical address of the location being written
   */
  virtual void WriteBuffer(uint8_t buf[SV_MEM_WIDTH_BYTES],
                           const std::vector<uint8_t> &data, size_t start_idx,
                           uint32_t dst_word) const;

  /** Extract the logical memory contents corresponding to the physical
   * memory contents in \p buf and append them to \p data.
   *
   * The default implementation just uses \c std::copy_n to copy the data
   * across. Other implementations might undo scrambling, remove ECC bits or
   * similar.
   *
   * @param data     The target, onto which the extracted memory contents
   *                 should be appended.
   *
   * @param buf      Source buffer (physical memory bits)
   *
   * @param src_word Logical address of the location being read
   */
  virtual void ReadBuffer(std::vector<uint8_t> &data,
                          const uint8_t buf[SV_MEM_WIDTH_BYTES],
                          uint32_t src_word) const;

  /** Convert a logical address to physical address
   *
   * Some memories may have a mapping between the address supplied on the
   * request and the physical address used to access the memory array (for
   * example scrambled memories). By default logical and physical address are
   * the same.
   */
  virtual uint32_t ToPhysAddr(uint32_t logical_addr) const {
    return logical_addr;
  }

  /** Read the memory word at phys_addr into minibuf
   *
   * minibuf should be at least SV_MEM_WIDTH_BYTES in size. See the
   * implementation of MemArea::Write() for the details.
   */
  void ReadToMinibuf(uint8_t *minibuf, uint32_t phys_addr) const;

  /** Write from minibuf to the memory word at phys_addr
   *
   * minibuf should be at least SV_MEM_WIDTH_BYTES in size. See the
   * implementation of MemArea::Write() for the details.
   */
  void WriteFromMinibuf(uint32_t phys_addr, const uint8_t *minibuf,
                        uint32_t dst_word) const;
};

#endif  // OPENTITAN_HW_DV_VERILATOR_CPP_MEM_AREA_H_
