// Copyright 2024-2025 RnD Center "ELVEES", JSC

/*! \file
 *  \brief Заголовочный файл c функиями для работы с тайловой сегментацией
 *  \author Фролов Андрей
 */

#ifndef TILE_SEGMENTATION_H
#define TILE_SEGMENTATION_H

//! адресс локальной памяти
extern int __local_mem;

#include <math.h>
#include <stdint.h>

#include <iostream>

#include "elcore50-dsplib/dmainit.h"

extern "C" {
#include "elcore50-dsplib/elcore50.h"
}

/** \struct TileSegConfig
 *  \brief структура для параметризации запуска функции с тайловой сегментацией.
    Используются в общем случае четыре цепочки DMA. По две на входные и выходные
    данные.
 */
typedef struct TileSegConfig {
  TileSegConfig();
  ~TileSegConfig();
  //! указатель на цепочку 0
  VDMAChain* chain0;
  //! указатель на цепочку 1
  VDMAChain* chain1;
  //! указатель на цепочку 2
  VDMAChain* chain2;
  //! указатель на цепочку 3
  VDMAChain* chain3;
  //! длина цепочки 0
  int chain0_length;
  //! длина цепочки 1
  int chain1_length;
  //! длина цепочки 2
  int chain2_length;
  //! длина цепочки 3
  int chain3_length;
  //! указатель на параметры функции (пр. длины каждого из тайлов)
  int* func_params;
  //! к-во элементов func_params
  int func_params_length;
  //! указатели на буферы во внут. памяти для входных данных
  uint8_t* ptr_src_buf[2];
  //! указатели на буферы во внут. памяти для выходных данных
  uint8_t* ptr_dst_buf[2];
  //! указатель для возвращаемого значения вычислительного кернела
  int64_t* return_value;
  /*! дополнительный указатель для возвращаемого значения вычислительного
      кернела, например при возврате двух чисел */
  int64_t* return_value1;
  //! дополнительный указатель входного коэффициента
  int64_t* param0;
  //! размер тайла
  size_t tile_length;

 private:
  TileSegConfig(const TileSegConfig&);
  TileSegConfig& operator=(const TileSegConfig&);
} TileSegConfig;

/*!
 *  \fn void CreateTileSegConfig(TileSegConfig* config)
 *  \brief Функция для начальной инициализации структуры TileSegConfig
 *  \param[in/out] config указатель на структуру для параметризации запуска
 */
void CreateTileSegConfig(TileSegConfig* config);

/*!
 *  \fn void DestroyTileSegConfig(TileSegConfig* config)
 *  \brief Функция для очистки параметров структуры TileSegConfig
 *  \param[in/out] config указатель на структуру для параметризации запуска
 */
void DestroyTileSegConfig(TileSegConfig* config);

/*!
 *  \fn void CreateChains1D(void **src0, int32_t input_num,
                            void **dst, int32_t output_num,
                            const int32_t size, const int32_t element_size,
                            TileSegConfig* result_chains, void* local_mem_ptr)
 *  \brief Функция формирования цепочек и параметров для запуска вычислительных
           кернелов. В частном случае формируются четыре цепочки. По две на вход
           и выход. 0 и 1 цепочки входных данных - load, 2,3 - store.
 *  \param[in] src0 - указатель на массив указателей входных массивов
 *  \param[in] input_num - размер массива входных указателей
 *  \param[in] dst - указатель на массив указателей выходных массивов
 *  \param[in] output_num - размер массива выходных указателей
 *  \param[in] size - длина массива входного
 *  \param[in] element_size - размер в байтах одного элемента входного массива
 *  \param[in/out] result_chains указатель на структуру для параметризации
                   запуска
 *  \param[in] local_mem_ptr - указатель на внут. память
 */
void CreateChains1D(void** src0, int32_t input_num, void** dst, int32_t output_num, const int32_t size,
                    const int32_t element_size, TileSegConfig* result_chains, void* local_mem_ptr);
/*!
 *  \fn void RunCalculation(const TileSegConfig* dma_chains, func_ptr calculation)
 *  \brief Функция запуска потайловой работы кернела.
           Пока отрабатывает кернел на тайле с данными первых цепочек,
           подгружаются вторые. (Двубуферная схема)

    Примерная схема работы:

     time  | Actions
    -------|---------
     0     | ldch0
     1     | ldch1  calc_0_2
     2     | ldch0  calc_1_3  stch2
     3     | ldch1  calc_0_2  stch3
    ...    | ...
 *  \param[in] dma_chains - указатель на структуру для параметризации запуска
 *  \param[in] calculation - указатель на вычислительный кернел
 */
template <class func_ptr>
void RunCalculation(const TileSegConfig* dma_chains, func_ptr calculation) {
#ifdef DEBUG
  printf("func_params_length = %d\n", dma_chains->func_params_length);
#endif

  // статус работы цепи 0 - цепочка не в работе, 1 - работает
  int run_chain_ld[2] = {0, 0};
  int run_chain_st[2] = {0, 0};

  // смещение для номера цепей выгрузки данных, 2,3 - номера цепей
  int st_chain_stride = 2;

  int ld_chain_length[2] = {dma_chains->chain0_length, dma_chains->chain1_length};

  int st_chain_length[2] = {dma_chains->chain2_length, dma_chains->chain3_length};

  // флаги первого запуска цепей
  bool first_start_st[2] = {true, true};

  if (!run_chain_ld[0] && dma_chains->chain0_length) {
    startchain(dma_chains->chain0, 0);
    run_chain_ld[0] = 1;
    ld_chain_length[0]--;
  }

  if (!run_chain_ld[1] && dma_chains->chain1_length) {
    startchain(dma_chains->chain1, 1);
    run_chain_ld[1] = 1;
    ld_chain_length[1]--;
  }

  int buf_num = 0;
  for (int i = 0; i < dma_chains->func_params_length; ++i) {
    buf_num = i % 2;

    // ожидание данных необходимых для вычислений
    if (run_chain_ld[buf_num]) {
      waitchain(buf_num);
      run_chain_ld[buf_num] = 0;
    }

    if (run_chain_st[buf_num]) {
      waitchain(st_chain_stride + buf_num);
      run_chain_st[buf_num] = 0;
    }

#ifdef DEBUG
    printf("calculation(%d, %d)\n", buf_num, buf_num);
#endif

    calculation(dma_chains, buf_num, i);

    // выгрузка результата
    if (!run_chain_st[buf_num] && st_chain_length[buf_num]) {
      if (first_start_st[buf_num]) {
        first_start_st[buf_num] = false;
        if (!buf_num)
          startchain(dma_chains->chain2, st_chain_stride);
        else
          startchain(dma_chains->chain3, st_chain_stride + 1);
      } else {
        if (!run_chain_st[buf_num] && st_chain_length[buf_num]) {
          resumechain(st_chain_stride + buf_num);
        }
      }

      st_chain_length[buf_num]--;
      run_chain_st[buf_num] = 1;
    }

    // запуск следующего блока необходимых данных
    if (!run_chain_ld[buf_num] && ld_chain_length[buf_num]) {
      resumechain(buf_num);
      run_chain_ld[buf_num] = 1;
      ld_chain_length[buf_num]--;
    }
  }

  // выгрузка последнего результата
  if (run_chain_st[buf_num]) {
    waitchain(st_chain_stride + buf_num);
    run_chain_st[buf_num] = 0;
  }
}

// #define PCU12_OFFSET 0x800
// #define InvCtrl ((volatile uint32_t *)(E50BASER+PCU12_OFFSET+0x4))
// /*!
//  *  \fn void flush_all_caches()
//  *  \brief Функция для сброса кэша
//  */
// static void flush_all_caches()
// {
//     asm volatile("mbar 0x0");
//     asm volatile("sev0 18");
//     asm volatile("sev0 19");
//     *InvCtrl = 0xffff;
//     asm volatile("wfe1 18");
//     asm volatile("wfe1 19");
//     asm volatile("sev0 18");
//     asm volatile("sev0 19");
//     asm volatile("mbar 0x0");
// }

#endif
