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

#include <string>
#include <vector>

#ifndef COSIM_H_
#define COSIM_H_

// Information about a dside transaction observed on the DUT memory interface
struct DSideAccessInfo {
  // set when the access is a store, in which case `data` is the store data from
  // the DUT. Otherwise `data` is the load data provided to the DUT.
  bool store;
  // `addr` is the address and must be 32-bit aligned. `data` and `be` are
  // aligned to the address. For example an 8-bit store of 0xff to 0x12 has
  // `data` as 0x00ff0000, `addr` as 0x10 and `be` as 0b0100.
  uint32_t data;
  uint32_t addr;
  uint32_t be;

  // set if an error response to the transaction is seen.
  bool error;

  // `misaligned_first` and `misaligned_second` are set when the transaction is
  // generated for a misaligned store or load instruction.  `misaligned_first`
  // is true when the transaction is for the lower half and `misaligned_second`
  // is true when the transaction is for the upper half, if it exists.
  //
  // For example an unaligned 32-bit load to 0x3 produces a transaction with
  // `addr` as 0x0 and `misaligned_first` set to true, then a transaction with
  // `addr` as 0x4 and `misaligned_second` set to true. An unaligned 16-bit load
  // to 0x01 only produces a transaction with `addr` as 0x0 and
  // `misaligned_first` set to true, there is no second half.
  bool misaligned_first;
  bool misaligned_second;

  bool misaligned_first_saw_error;

  bool m_mode_access;
};

class Cosim {
 public:
  virtual ~Cosim() {}

  // Add a memory to the co-simulator environment.
  //
  // Use `backdoor_write_mem`/`backdoor_read_mem` to access it from the
  // simulation environment.
  virtual void add_memory(uint32_t base_addr, size_t size) = 0;

  // Write bytes to co-simulator memory.
  //
  // returns false if write fails (e.g. because no memory exists at the bytes
  // being written).
  virtual bool backdoor_write_mem(uint32_t addr, size_t len,
                                  const uint8_t *data_in) = 0;

  // Read bytes from co-simulator memory.
  //
  // returns false if read fails (e.g. because no memory exists at the bytes
  // being read).
  virtual bool backdoor_read_mem(uint32_t addr, size_t len,
                                 uint8_t *data_out) = 0;

  // Step the co-simulator, checking register write and PC of executed
  // instruction match the supplied values. `write_reg` gives the index of the
  // written register along with `write_reg_data` which provides the data. A
  // `write_reg` of 0 indicates no register write occurred.
  //
  // `sync_trap` is set to true when the instruction caused a synchronous trap.
  // In this case the instruction doesn't retire so no register write occurs (so
  // `write_reg` must be 0).
  //
  // Returns false if there are any errors; use `get_errors` to obtain details
  virtual bool step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc,
                    bool sync_trap, bool suppress_reg_write) = 0;

  // When more than one of `set_mip`, `set_nmi` or `set_debug_req` is called
  // before `step` which one takes effect is chosen by the co-simulator. Which
  // should take priority is architecturally defined by the RISC-V
  // specification.

  // Set the value of MIP.
  //
  // Two versions of MIP must be supplied the `pre_mip` and the `post_mip`. The
  // `pre_mip` is the value of MIP that is used to determine if an interrupt is
  // pending. The `post_mip` is the value of MIP that the next instruction
  // executed (which will be the first instruction of the interrupt vector when
  // an interrupt ir triggered) observes. These will be different in the case
  // where an interrupt is raised triggering an interrupt handler but then
  // drops before the first instruction of the handler has executed.
  //
  // At the next call of `step`, the MIP values will take effect (i.e. if it's a
  // new interrupt that is enabled it will step straight to that handler).
  virtual void set_mip(uint32_t pre_mip, uint32_t post_mip) = 0;

  // Set the state of the NMI (non-maskable interrupt) line.
  //
  // The NMI signal is a level triggered interrupt. When the NMI is taken the
  // CPU ignores the NMI line until an `mret` instruction is executed. If the
  // NMI is high following the `mret` (regardless of whether it has been low or
  // not whilst the first NMI is being handled) a new NMI is taken.
  //
  // When an NMI is due to be taken that will occur at the next call of `step`.
  virtual void set_nmi(bool nmi) = 0;

  // Set the state of the internal NMI (non-maskable interrupt) line.
  // Behaviour wise this is almost as same as external NMI case explained at
  // set_nmi method. Difference is that this one is a response from Ibex rather
  // than an input.
  virtual void set_nmi_int(bool nmi_int) = 0;

  // Set the debug request.
  //
  // When set to true the core will enter debug mode at the next step
  virtual void set_debug_req(bool debug_req) = 0;

  // Set the value of mcycle.
  //
  // The co-simulation model doesn't alter the value of mcycle itself (other
  // than instructions that do a direct CSR write). mcycle should be set to the
  // correct value before any `step` call that may execute an instruction that
  // observes the value of mcycle.
  //
  // A full 64-bit value is provided setting both the mcycle and mcycleh CSRs.
  virtual void set_mcycle(uint64_t mcycle) = 0;

  // Set the value of a CSR. This is used when it is needed to have direct
  // communication between DUT and Spike (e.g. Performance counters).
  virtual void set_csr(const int csr_num, const uint32_t new_val) = 0;

  // Set the ICache scramble key valid bit that is visible in CPUCTRLSTS.
  virtual void set_ic_scr_key_valid(bool valid) = 0;

  // Tell the co-simulation model about observed transactions on the dside
  // memory interface of the DUT. Accesses are notified once the response to a
  // transaction is seen.
  //
  // Observed transactions for the DUT are checked against accesses from the
  // co-simulation model when a memory access occurs during a `step`. If there
  // is a mismatch an error is reported which can be obtained via `get_errors`.
  virtual void notify_dside_access(const DSideAccessInfo &access_info) = 0;

  // Tell the co-simulation model about an error response to an iside fetch.
  //
  // `addr` must be 32-bit aligned.
  //
  // The next step following a call to `set_iside_error` must produce an
  // instruction fault at the given address.
  virtual void set_iside_error(uint32_t addr) = 0;

  // Get a vector of strings describing errors that have occurred during `step`
  virtual const std::vector<std::string> &get_errors() = 0;

  // Clear internal vector of error descriptions
  virtual void clear_errors() = 0;

  // Returns a count of instructions executed by co-simulator and DUT without
  // failures.
  virtual unsigned int get_insn_cnt() = 0;
};

#endif  // COSIM_H_
