// Copyright © 2019-2023
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include <unordered_map>
#include <vector>
#include <mutex>
#include <memory>
#include <iostream>

#include "svdpi.h"
// #include "verilated_vpi.h"

#include "uuid_gen.h"

#ifdef XLEN_64
#define iword_t   int64_t
#define uword_t   uint64_t
#define idword_t  __int128_t
#define udword_t  __uint128_t
#else
#define iword_t   int32_t
#define uword_t   uint32_t
#define idword_t  int64_t
#define udword_t  uint64_t
#endif

#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL 3
#endif

extern "C" {
  void dpi_imul(bool enable, bool is_signed_a, bool is_signed_b, iword_t a, iword_t b, iword_t* resultl, iword_t* resulth);
  void dpi_idiv(bool enable, bool is_signed, iword_t a, iword_t b, iword_t* quotient, iword_t* remainder);

  int dpi_register();
  void dpi_assert(int inst, bool cond, int delay);

  void dpi_trace(int level, const char* format, ...);
  void dpi_trace_start();
  void dpi_trace_stop();

  uint64_t dpi_uuid_gen(bool reset, int wid, uint64_t PC);
}

bool sim_trace_enabled();
void sim_trace_enable(bool enable);

class ShiftRegister {
public:
  ShiftRegister() : init_(false), depth_(0) {}

  void ensure_init(int depth) {
    if (!init_) {
      buffer_.resize(depth);
      init_  = true;
      depth_ = depth;
    }
  }

  void push(int value, bool enable) {
    if (!enable)
      return;      
    for (unsigned i = 0; i < depth_-1; ++i) {
      buffer_[i] = buffer_[i+1];
    }
    buffer_[depth_-1] = value;
  }

  int top() const {
    return buffer_[0];
  }

private:

  std::vector<int> buffer_;
  bool init_;
  unsigned depth_;  
};

class Instances {
public:
  ShiftRegister& get(int inst) {
    return instances_.at(inst);
  }

  int allocate() {
    mutex_.lock();   
    int inst = instances_.size();
    instances_.resize(inst + 1); 
    mutex_.unlock();
    return inst;
  }

private:
  std::vector<ShiftRegister> instances_;
  std::mutex mutex_;
};

Instances instances;

int dpi_register() {
  return instances.allocate();
}

void dpi_assert(int inst, bool cond, int delay) {
  ShiftRegister& sr = instances.get(inst);

  sr.ensure_init(delay);
  sr.push(!cond, 1);

  auto status = sr.top();
  if (status) {
    printf("delayed assertion at %s!\n", svGetNameFromScope(svGetScope()));
    std::abort();
  }
}

///////////////////////////////////////////////////////////////////////////////

void dpi_imul(bool enable, bool is_signed_a, bool is_signed_b, iword_t a, iword_t b, iword_t* resultl, iword_t* resulth) {
  if (!enable)
    return;
  udword_t first  = *(uword_t*)&a;
  udword_t second = *(uword_t*)&b;

  udword_t mask = udword_t(-1) << (8 * sizeof(iword_t));
    
  if (is_signed_a && a < 0) {
    first |= mask;
  }

  if (is_signed_b && b < 0) {
    second |= mask;
  }

  udword_t result;
  if (is_signed_a || is_signed_b) {
    result = idword_t(first) * idword_t(second);
  } else {
    result = first * second;
  }

  *resultl = iword_t(result);
  *resulth = iword_t(result >> (8 * sizeof(iword_t)));
}

void dpi_idiv(bool enable, bool is_signed, iword_t a, iword_t b, iword_t* quotient, iword_t* remainder) {
  if (!enable)
    return;

  uword_t dividen = a;
  uword_t divisor = b;

  auto inf_neg = uword_t(1) << (8 * sizeof(iword_t) - 1);

  if (is_signed) {
    if (b == 0) {
      *quotient  = -1;
      *remainder = dividen;
    } else if (dividen == inf_neg && divisor == -1) {
      *remainder = 0;
      *quotient  = dividen;
    } else { 
      *quotient  = (iword_t)dividen / (iword_t)divisor;
      *remainder = (iword_t)dividen % (iword_t)divisor;      
    }
  } else {    
    if (b == 0) {
      *quotient  = -1;
      *remainder = dividen;
    } else {
      *quotient  = dividen / divisor;
      *remainder = dividen % divisor;
    }
  }
}

///////////////////////////////////////////////////////////////////////////////

void dpi_trace(int level, const char* format, ...) { 
  if (level > DEBUG_LEVEL)
    return;
  // if (!sim_trace_enabled())
  //   return;
  va_list va;
	va_start(va, format);  
	vprintf(format, va);
	va_end(va);		  
}

void dpi_trace_start() { 
  // sim_trace_enable(true);
}

void dpi_trace_stop() { 
  // sim_trace_enable(false);
}

///////////////////////////////////////////////////////////////////////////////

std::unordered_map<uint32_t, std::shared_ptr<vortex::UUIDGenerator>> g_uuid_gens;

uint64_t dpi_uuid_gen(bool reset, int wid, uint64_t PC) {
  if (reset) {
    g_uuid_gens.clear();
    return 0;
  }
  std::shared_ptr<vortex::UUIDGenerator> uuid_gen;
  auto it = g_uuid_gens.find(wid);
  if (it == g_uuid_gens.end()) {
    uuid_gen = std::make_shared<vortex::UUIDGenerator>();
    g_uuid_gens.emplace(wid, uuid_gen);
  } else {
    uuid_gen = it->second;
  }
  uint32_t instr_uuid = uuid_gen->get_uuid(PC);
  uint32_t instr_id  = instr_uuid & 0xffff;
  uint32_t instr_ref = instr_uuid >> 16;
  uint64_t uuid = (uint64_t(instr_ref) << 32) | (wid << 16) | instr_id;
  return uuid;
}
