/* GPU2RTDS IP core
 *
 * Author: Daniel Krebs <github@daniel-krebs.net>
 * SPDX-FileCopyrightText: 2017 Daniel Krebs <github@daniel-krebs.net>
 * SPDX-License-Identifier: Apache-2.0
 */

#include <cstring>
#include <unistd.h>

#include <villas/fpga/ips/rtds2gpu.hpp>
#include <villas/log.hpp>
#include <villas/memory_manager.hpp>

using namespace villas::fpga::ip;

bool Rtds2Gpu::init() {
  Hls::init();

  xInstance.IsReady = XIL_COMPONENT_IS_READY;
  xInstance.Ctrl_BaseAddress = getBaseAddr(registerMemory);

  status.value = 0;
  started = false;

  //	maxFrameSize = getMaxFrameSize();
  maxFrameSize = 16;
  logger->info("Max. frame size supported: {}", maxFrameSize);

  return true;
}

void Rtds2Gpu::dump(spdlog::level::level_enum logLevel) {
  const auto baseaddr = XRtds2gpu_Get_baseaddr(&xInstance);
  const auto data_offset = XRtds2gpu_Get_data_offset(&xInstance);
  const auto doorbell_offset = XRtds2gpu_Get_doorbell_offset(&xInstance);
  const auto frame_size = XRtds2gpu_Get_frame_size(&xInstance);

  logger->log(logLevel, "Rtds2Gpu registers (IP base {:#x}):",
              xInstance.Ctrl_BaseAddress);
  logger->log(logLevel, "  Base address (bytes):     {:#x}", baseaddr);
  logger->log(logLevel, "  Doorbell offset (bytes):  {:#x}", doorbell_offset);
  logger->log(logLevel, "  Data offset (bytes):      {:#x}", data_offset);
  logger->log(logLevel, "  Frame size (words):       {:#x}", frame_size);
  logger->log(logLevel, "  Status:                   {:#x}", status.value);
  logger->log(logLevel, "    Running:            {}",
              (status.is_running ? "yes" : "no"));
  logger->log(logLevel, "    Frame too short:    {}",
              (status.frame_too_short ? "yes" : "no"));
  logger->log(logLevel, "    Frame too long:     {}",
              (status.frame_too_long ? "yes" : "no"));
  logger->log(logLevel, "    Frame size invalid: {}",
              (status.invalid_frame_size ? "yes" : "no"));
  logger->log(logLevel, "    Last count:         {}", (int)status.last_count);
  logger->log(logLevel, "    Last seq. number:   {}", (int)status.last_seq_nr);
  logger->log(logLevel, "    Max. frame size:    {}",
              (int)status.max_frame_size);
}

bool Rtds2Gpu::startOnce(const MemoryBlock &mem, size_t frameSize,
                         size_t dataOffset, size_t doorbellOffset) {
  auto &mm = MemoryManager::get();

  if (frameSize > maxFrameSize) {
    logger->error("Requested frame size of {} exceeds max. frame size of {}",
                  frameSize, maxFrameSize);
    return false;
  }

  auto translationFromIp = mm.getTranslation(
      getMasterAddrSpaceByInterface(axiInterface), mem.getAddrSpaceId());

  // Set address of memory block in HLS IP
  XRtds2gpu_Set_baseaddr(&xInstance, translationFromIp.getLocalAddr(0));

  XRtds2gpu_Set_doorbell_offset(&xInstance, doorbellOffset);
  XRtds2gpu_Set_data_offset(&xInstance, dataOffset);
  XRtds2gpu_Set_frame_size(&xInstance, frameSize);

  // Prepare memory with all zeroes
  auto translationFromProcess =
      mm.getTranslationFromProcess(mem.getAddrSpaceId());
  auto memory =
      reinterpret_cast<void *>(translationFromProcess.getLocalAddr(0));
  memset(memory, 0, mem.getSize());

  // Start IP
  return start();
}

bool Rtds2Gpu::updateStatus() {
  if (not XRtds2gpu_Get_status_vld(&xInstance))
    return false;

  status.value = XRtds2gpu_Get_status(&xInstance);

  return true;
}

size_t Rtds2Gpu::getMaxFrameSize() {
  XRtds2gpu_Set_frame_size(&xInstance, 0);

  start();
  while (not isFinished())
    ;
  updateStatus();

  return status.max_frame_size;
}

void Rtds2Gpu::dumpDoorbell(uint32_t doorbellRegister) const {
  auto &doorbell = reinterpret_cast<reg_doorbell_t &>(doorbellRegister);

  logger->info("Doorbell register: {:#08x}", doorbell.value);
  logger->info("  Valid:       {}", doorbell.is_valid ? "yes" : "no");
  logger->info("  Count:       {}", (int)doorbell.count);
  logger->info("  Seq. number: {}", (int)doorbell.seq_nr);
}

static char n[] = "Rtds2Gpu";
static char d[] = "HLS RTDS2GPU IP";
static char v[] = "acs.eonerc.rwth-aachen.de:hls:rtds2gpu:";
static NodePlugin<Rtds2Gpu, n, d, v> f;