// Copyright lowRISC contributors (OpenTitan project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef OPENTITAN_HW_DV_VERILATOR_CPP_DPI_MEMUTIL_H_
#define OPENTITAN_HW_DV_VERILATOR_CPP_DPI_MEMUTIL_H_

#include <map>
#include <memory>
#include <string>
#include <svdpi.h>
#include <vector>

#include "mem_area.h"
#include "ranged_map.h"

// Forward declaration for the Elf type from libelf.
struct Elf;

enum MemImageType {
  kMemImageUnknown = 0,
  kMemImageElf,
  kMemImageVmem,
};

// Staged data for a given memory area.
//
// This is represented as an ordered list of disjoint segments (as loaded from
// an ELF file).
//
// Once it is nonempty, the class maintains the invariant that min_addr_ /
// max_addr_ is the smallest / largest byte offset with valid data.
class StagedMem {
 public:
  StagedMem() : min_addr_(~(uint32_t)0), max_addr_(0) {}

  // Add a segment to the tracked memory
  void AddSegment(uint32_t offset, std::vector<uint8_t> &&seg);

  // Glob together the tracked segments, interspersing them with
  // zeros, and return as a single flat array.
  std::vector<uint8_t> GetFlat() const;

  typedef RangedMap<uint32_t, std::vector<uint8_t>> SegMap;

  std::pair<uint32_t, uint32_t> GetBounds() const {
    return std::make_pair(min_addr_, max_addr_);
  }
  const SegMap &GetSegs() const { return segs_; }

 private:
  uint32_t min_addr_, max_addr_;
  SegMap segs_;
};

/**
 * Provide various memory loading utilities for verilog simulations
 *
 * These utilities require the corresponding DPI functions:
 * simutil_memload()
 * simutil_set_mem()
 * to be defined somewhere as SystemVerilog functions.
 */
class DpiMemUtil {
 public:
  virtual ~DpiMemUtil() {}

  /**
   * Register a memory as instantiated by generic ram
   *
   * The |name| must be a unique identifier. The constructor will
   * throw a std::runtime_error if |name| is already used.
   *
   * |base| gives the base address of the memory in a logical address
   * space that corresponds to LMAs in an ELF file. If this overlaps
   * with some other registered memory, the constructor throws a
   * std::runtime_error.
   *
   * The |mem_area| argument describes the memory area to be registered and
   * must not be null. This function does not take ownership of the object,
   * which must survive at least as long as the DpiMemutil object.
   *
   * Memories must be registered before command arguments are parsed by
   * ParseCommandArgs() in order for them to be known.
   */
  void RegisterMemoryArea(const std::string &name, uint32_t base,
                          const MemArea *mem_area);

  /**
   * Guess the type of the file at |path|.
   *
   * If |type| is non-null, it is the name of an image type and will be used.
   * Otherwise, the check is based on |path|. If |type| is not a valid name or
   * if the function can't guess from the path, throws a std::runtime_error
   * with a message about what went wrong.
   *
   * Never returns kMemImageUnknown.
   */
  static MemImageType GetMemImageType(const std::string &path,
                                      const char *type);

  /**
   * Print a list of all registered memory regions
   *
   * @see RegisterMemoryArea()
   */
  void PrintMemRegions() const;

  /**
   * Load the file at filepath into the named memory. If type is
   * kMemImageUnknown, the file type is determined from the path.
   */
  void LoadFileToNamedMem(bool verbose, const std::string &name,
                          const std::string &filepath, MemImageType type);

  /**
   * Load an ELF file, placing segments in memories by LMA.
   *
   * Replaces any data currently in the staging area.
   */
  void LoadElfToMemories(bool verbose, const std::string &filepath);

  /**
   * Load an ELF file into a staging area in this object, which can then be
   * accessed with GetMemoryData().
   *
   * If the load fails, raises a std::exception with information about what
   * happened.
   */
  void StageElf(bool verbose, const std::string &path);

  /**
   * Get the contents of the staging area by memory name
   */
  const StagedMem &GetMemoryData(const std::string &mem_name) const;

 protected:
  /**
   * A hook for subclasses to do extra computations with loaded ELF data. This
   * runs as part of StageElf: after loading the ELF file, but before reading
   * in the segments.
   */
  virtual void OnElfLoaded(Elf *elf_file) {}

 private:
  // Memory area registry. The maps give indices pointing into the vectors
  // (which all have the same number of elements). Note that mem_areas_ does
  // not own the objects that it points to.
  std::vector<const MemArea *> mem_areas_;
  std::vector<uint32_t> base_addrs_;
  std::vector<std::string> names_;

  std::map<std::string, size_t> name_to_mem_;
  RangedMap<uint32_t, size_t> addr_to_mem_;

  // Staging area, loaded by StageElf. The map is keyed by names of memories
  // stored in name_to_mem_. We also ensure that every segment in a StagedMem
  // for a memory starts at an address that's aligned for the word width of
  // that memory. Note: we don't also check segments' lengths are aligned.
  std::map<std::string, StagedMem> staging_area_;
  const StagedMem empty_;

  /**
   * Find the index of a memory area containing the given segment's addresses.
   * Raises a std::exception if none is found.
   */
  size_t GetRegionForSegment(const std::string &path, int seg_idx, uint32_t lma,
                             uint32_t mem_sz) const;
};

#endif  // OPENTITAN_HW_DV_VERILATOR_CPP_DPI_MEMUTIL_H_
